Parameter Resolvers in Axon

Axon is an open-source Java framework for building systems in CQRS (Command Query Responsibility Separation), DDD (Domain Driven Design), and Event Sourcing. Axon provides a high level of location transparency, which can easily split the system into several Microservices. You can download the full open-source package here.

The Axon Framework is message-based - commands, events, and queries are supported types of messages. Therefore, we can define a handler (a method or a constructor) to handle these messages. In some cases, the message itself has enough information to be handled. Still, in many others, we have a dependency on other components and/or variables (message metadata such as correlation information would be a good example). Axon provides a potent mechanism to inject these dependencies into message handlers - Parameter Resolvers. This is the story about them. At the end of this article, you can find a cheat sheet of all resolvers provided by the Axon Framework.

Anatomy

Two important components support this mechanism: the ParameterResolver and the ParameterResolverFactory (see Figure 1, below).

parameter resolvers

The ParameterResolverFactory is used to create instances of the ParameterResolvers. Axon inspects messaging handling members (such as command handlers, event handlers, and query handlers) using reflection. We iterate through its parameter list for each of these handlers and try to find (using the ParameterResolverFactory) the corresponding ParameterResolver which can resolve the given parameter. Then, just before handling a concrete message, we resolve the message handler parameters using previously created resolvers. This is how the injection of specific fields and components into message handlers is done in Axon.

Wiring

There is an implementation of the ParameterResolverFactory, which is called MultiParameterResolverFactory. It is used to hold an array of ParameterResolverFactories and to delegate the creation of a specific ParameterResolver to them.

Service Loader is an out-of-the-box Java API that offers a specific form of Inversion of Control. It is designed to locate implementation classes of an interface on the classpath. This setup allows us to discover which available implementations of an interface are available on the classpath at runtime and thus paves the way for modules designed around a clean separation between an API module and multiple implementation modules. It’s implemented as a file located in the META-INF/services folder of a JAR. The file's name is the fully qualified name of the interface, while its content is a slit of qualified names of available implementations.

The ParameterResolverFactory can be configured using Configurer. The ClasspathParameterResolverFactory (not implementing ParameterResolverFactory) is a default way of providing ParameterResolverFactories to the framework using the Service Loader mechanism load all available implementations. All implementations are wrapped in the MultiParameterResolverFactory. This is also an extension point providing Axon Framework users with the means of defining their own ParameterResolverFactories.

Parameter Resolver Cheat Sheet

Figure 2 shows all ParameterResolverFactories provided by the framework.

parameter resolvers2The DefaultParameterResolverFactory is used to create:

  • a PayloadParameterResolver: resolves the payload of a message
  • a MessageParameterResolver: resolves the message as a whole
  • a MetaDataParameterResolver: resolves the key-value map of metadata values attached to a message
  • an AnnotatedMetaDataParameterResolver: resolves specific metadata value based on the provided key

Certain parameters are annotated, hence the AbstractAnnotatedParameterResolverFactory. Corresponding factories are:

  • The SequenceNumberParameterResolverFactory, which creates a SequenceNumberParameterResolver which is used to resolve sequence number of domain events (events that are published by an aggregate)

    @EventSourcingHandler
    public void on(MyEvent event, @SequenceNumber long sequenceNumber) {
          // handle domain event
    } 

  • The TimestampParameterResolverFactory, which creates a TimestampParameterResolver that is used to resolve the timestamp of an event message.

    @EventHandler
    public void on(MyEvent event, @Timestamp Instant timestamp) {
          // handle event
    }     
  • The MessageIdentifierParameterResolverFactory creates a MessageIdentifierParameterResolver used to resolve the identifier of a message (command, event, query, etc.).

    @MessageHandler
    public void handle(Message message, @MessageIdentifier String messageIdentifier) {
          // handle message
    }     
  • The ConcludesBatchParameterResolverFactory creates a ConcludesBatchParameterResolver used to resolve whether the given event is the last one in the batch of events being processed.

    @EventHandler
    public void on(MyEvent event, @ConcludesBatch Boolean concludesBatch)  {
          // handle event
    }

Besides the aforementioned resolvers, there are many more (shown in Figure 2), which, as they are self-explanatory, will not be treated in detail here. Of course, we can always implement our own Parameter Resolvers if necessary.

One example could be to have a query model entity ready during event processing. Think of the case where you have all kinds of User events handled by event-handling components, and for most of them, you’d need a User entity. We could do it by querying the UserRepository in those event handlers, which is not important to our business case and pollutes the logic. The other approach could be to create a UserParameterResolver which would query the UserRepository when a User event comes along and have a User ready to be injected as a parameter to our handler.

Conclusion

While most of the resources needed to handle a certain message can be obtained by different means, Parameter Resolvers provide an elegant way of doing so. You don’t have to write all of the boilerplate code to extract certain metadata values. For example, to get the information on whether your event handler is invoked for replaying purposes or to get the sequence number of your events, you need to declare what you need as a parameter in your message handler, and (if available) Parameter Resolver will resolve the value for you. Ultimately, all you have to worry about is your business case.

Check the Parameter Resolvers section in our reference guide for more technical information.

Milan Savic
Software Engineer. Milan builds the tools to help others build even-driven, reactive systems, based on his extensive experience in using CQRS and event sourcing.
Milan Savic

Share: