spring-modulith icon indicating copy to clipboard operation
spring-modulith copied to clipboard

Adding `spring-boot-starter-data-jpa` dependency stops `@ApplicationModuleListener` receiving events

Open timothysparg opened this issue 1 year ago • 6 comments

the following snippet works until I add the spring-boot-starter-data-jpa dependency.

@EnableScheduling
@SpringBootApplication
public class DemoApplication {

  private ApplicationEventPublisher publisher;

    public DemoApplication(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@ApplicationModuleListener
	public void onCustomEvent(CustomEvent event) {
		System.out.println("Event received!");
	}

	@Scheduled(timeUnit = TimeUnit.SECONDS, fixedRate = 5)
	public void fireEvents() {
		System.out.println("firing event");
		publisher.publishEvent(new CustomEvent());
	}

	record CustomEvent() {}
}

Is there something that I am missing ?

timothysparg avatar Jan 21 '24 17:01 timothysparg

when I add @Transactional then it works.

	@Transactional
	@Scheduled(timeUnit = TimeUnit.SECONDS, fixedRate = 5)
	public void fireEvents() {
		System.out.println("firing event");
		publisher.publishEvent(new CustomEvent());
	}

In hindsight when I look at the code snippets in the events reference page I can see the implication that all events need to be transacted, but maybe it should be more explict?

timothysparg avatar Jan 21 '24 18:01 timothysparg

I am inclined your original code won't work without the JPA starter either as @ApplicationModuleListener is a @TransactionalEventListener in standard Spring terms. Thus, without the scheduled method producing the event within some transaction, it won't actually get invoked anyway.

odrotbohm avatar Jan 29 '24 17:01 odrotbohm

Hi Oliver,

Apologies for the delayed response, I was on holiday 🙂

I've created the a repo where I replicated the above. https://github.com/timothysparg/sm-477

If you run that app without any modifications it should fire and receive events. If you uncomment the spring-boot-starter-data-jpa dependency in the pom then it will never receive any events.

timothysparg avatar Feb 11 '24 19:02 timothysparg

@timothysparg I ran into the exact same issue. The issue is, that the method that produces publishes your events needs to be @Transactional. You've already mentioned that in your past comment.

As far as I understood it, as long as you don't add a database starter to your Modulith project, Modulith won't try to persist your events and thus no @Transactional is needed, because the event handling won't (can't?) be backed by an event publication registry.

As soon as a database starter is part of your project it will automatically connect to your datasource to keep your events in a table.

This may be an oversight in autoconfiguration and/or documentation.

Arondc avatar Feb 11 '24 20:02 Arondc

As far as I understood it, as long as you don't add a database starter to your Modulith project, Modulith won't try to persist your events and thus no @Transactional is needed, because the event handling won't (can't?) be backed by an event publication registry.

I think you need to add an explicit modulith db starter for it to persist events to the database. Or at least that is the behaviour that I have seen.

This may be an oversight in autoconfiguration and/or documentation.

Broadly this is what I am trying to understand - if it is the expected behaviour I feel like it should be more explicit and if possible shouldn't just silently fail. There will be other people trying to get started who run into the same thing.

timothysparg avatar Feb 22 '24 18:02 timothysparg

Thanks, everyone, for chiming in here. I dug into the example and here are my findings.

  • Fundamentally, an @ApplicationModuleListener is a @TransactionalEventListener that only gets triggered on transaction commit. In other words, without a transaction running, those should and usually will not be invoked.
  • The reason you see the listener invoked without a transaction is that, in its original state, the reproducer project, does not contain any database-related dependencies which would implicitly cause @EnableTransactionManagement to be activated. Without that in place, the transactional event listener is detected and registered as an ordinary event listener and directly invoked for the event publication.
  • This also explains why adding the Spring Modulith JPA starter dependency turns the project back to behaving as intended (the event not reaching the listener at all). The presence of the database-related dependencies implicitly activates the proper handling of the listener.

I've filed spring-projects/spring-framework#32319 to fix the transactional event listener handling in case no transactional infrastructure is enabled. I am inclined to close this as working as designed.

odrotbohm avatar Feb 23 '24 10:02 odrotbohm