Micronaut: multi-module communication with events

Recently I wrote a post on how to organize and configure a multi-module project with maven. This short post will show us how to communicate between modules using Micronaut’s event system.

Micronaut provides a publish/subscribe general event system that is used by itself to publish events such as when the application has started or is shutting down. We can leverage this system to decouple communication between modules. The system is composed of two APIs:

  • ApplicationEventPublisher: is used to publish events. All Micronaut’s events extend one class named ApplicationEvent, but the system supports events of any kind, like the ones we’ll create here.
  • ApplicationEventListener: is used to listen to events.

Now, let’s remember the structure of the multi-module project:

micronaut-multi-module
|- account
|- core 
|- startup
|- pom.xml

The idea here is to listen to events throughout the application. Achieving that is possible through our module core, where the events should be centralized within the one module that is shared among all. One module to bind them.

Publishing an event

Before we start publishing events, we need an event itself. Following the structure of the previous post, let’s create an event:

public class AccountCreatedEvent {

  private final Account account;

  public AccountCreatedEvent(Account account) {
    Objects.requireNonNull(account, "Account must not be null");
    this.account = account;
  }

  public Account getAccount() {
    return account;
  }
}

Notice that is not necessary to extend ApplicationEvent. Publishing it now can be achieved by injecting ApplicationEventPublisher into our class of interest:

@Controller("/accounts")
public class AccountController {
  @Inject private ApplicationEventPublisher eventPublisher;
}

Now that we have it, publishing an event is simple as this:

@Post
public void post(AccountData accountData) {
  var uuid = UUID.randomUUID();
  var account = new Account(uuid.toString(), accountData.getName());
  eventPublisher.publishEvent(new AccountCreatedEvent(account));
}

Listening to an event

In the previous section, an event was published, but we missed because we didn’t have this section yet. We can listen to the events by implementing the interface ApplicationEventListener or by annotating a method with the @EventListener annotation.

The interface

@Singleton
public class AccountCreatedEventListener 
    implements ApplicationEventListener<AccountCreatedEvent> {
    
  @Override
  public void onApplicationEvent(AccountCreatedEvent event) {
    var account = event.getAccount();
    System.out.println("(Interface) Account created: " + account.getName());
  }
}

The method

@Singleton
public class AccountEventHandler {

  @EventListener
  public void on(AccountCreatedEvent event) {
    var account = event.getAccount();
    System.out.println("(Method) Account created: " + account.getName());
  }
}

I particularly prefer the second way, specially if there are more events related. Important to notice that the events with this configuration execute synchronously, so it’s important to be careful with the size of the task we’ll handle. If that’s the case, Micronaut provides a way of executing this task asynchronously by using the annotation @Async together with @EventListener:

@Async
@EventListener
public void on(AccountCreatedEvent event) {
  var account = event.getAccount();
  System.out.println("(Method) Account created: " + account.getName());
}

This way, the execution of the event will be moved to a different thread that runs on the default scheduled executor. The possible options are scheduled, cached, fixed or work stealing. Configuring the executor can be done by changing the configuration within the application.yml file:

micronaut:
  executors:
    scheduled:
        type: cached
        core-pool-size: 30

And just like that, we have our event being published and consumed using Micronaut’s general event system.

Conclusion

Events are a good way of decoupling code and can be easily used throughout the application. This post was written as a way of sharing this good piece of functionality from Micronaut ♥.

The source code for this post can be found on GitHub.

Thank you for reading!