Smooth implementation of CQRS/ES with Spring Boot and Axon framework

Łukasz Kucik
September 21, 2018

In the last article, I described what CQRS and Event Sourcing are and what problems they solve. You can find there all the necessary details to start the implementation of these concepts. Even though CQRS/ES can be implemented without any additional frameworks or libraries, I would recommend using one of the available tools. It could ease of development process while allowing stay focused on business logic. Our choice has fallen upon the Axon framework.

What is Axon?

Axon is a Java lightweight open source framework which helps build scalable, extensible and maintainable applications based on the CQRS pattern. It can also support you in preparing your system for event sourcing. Axon provides an implementation of all significant building blocks like aggregates, repositories, commands and event buses. Axon is just about making life easier for developers.

Why Axon?

Axon allows us to forget about configuration and data flow. Instead of adding boilerplate, we can stay focus on the business rules of our application. We can distinguish some of the benefits:

  • Correct event processing. We should be aware that some of the events should be delivered earlier and some of them later. Axon guarantees to supply events to the right event handlers and process them concurrently and in the correct order.
  • Built-in test environment. Axon framework provides a test fixture that allows you to compose tests in given-when-then style. It makes unit testing easy.
  • Spring Boot AutoConfiguration. The easiest way of configuring Axon in a Spring application. The only one necessary thing is to add an appropriate dependency. Axon will automatically configure some of the essential components.
  • Annotation support. Axon provides annotation support which makes our code cleaner and we can build aggregates and event handlers without getting Axon specific logic.

A quick configuration in Spring Boot application

Integration with Spring Boot is provided by default. The only thing we have to do is to take some steps to configure some necessary beans.

Step 1

The first step is to configure Axon dependency in the project using an appropriate build tool. Here is how you can do it with Gradle:

dependencies {
  compile('org.axonframework:axon-spring-boot-starter:3.2')
  compile('org.axonframework:axon-mongo:3.2')
  testCompile('org.axonframework:axon-test:3.2')
}

The first dependency gives us basic Axon functionality integrated with Spring. All the necessary components such as command bus, event bus, and aggregates. The second dependency is necessary to configure repositories for our aggregate or events (event store). The last dependency is related to providing building blocks for testing our aggregates.

Step 2

The configuration of the Axon is straightforward. You have to configure some Spring beans. We’ve configured EventHandlingConfiguration (component responsible for controlling event handlers behavior) to abort processing all subsequent events if execution of one of them fails. This is, of course, additional configuration, but it is worth doing to avoid inconsistencies in the system.

@Configuration
public class AxonConfig {

private final EventHandlingConfiguration eventHandlingConfiguration;

@Autowired
public AxonConfig(EventHandlingConfiguration eventHandlingConfiguration) {
   this.eventHandlingConfiguration = eventHandlingConfiguration;
}

@PostConstruct
public void registerErrorHandling() {
   eventHandlingConfiguration.configureListenerInvocationErrorHandler(configuration -> (exception, event, listener) -> {
       String msg = String.format(
               "[EventHandling] Event handler failed when processing event with id %s. Aborting all further event handlers.",
               event.getIdentifier());
       log.error(msg, exception);
       throw exception;
   });
}}

The main idea here is to create an additional configuration file (class annotated with @Configuration). The constructor of this class injects EventHandlingConfiguration dependency which is managed by Spring itself. Thanks to tied dependency we can call configureListenerInvocationErrorHandler() on this object and handle errors by logging and propagating exceptions to upper levels.

Step 3

We use the event store to keep all emitted events in MongoDB. To create such a repository, configure the following bean:

@Bean
public EventStorageEngine eventStore(MongoTemplate mongoTemplate) {
   return new MongoEventStorageEngine(
           new JacksonSerializer(), null, mongoTemplate, new DocumentPerEventStorageStrategy());
}

With that, all events published on event bus will be automatically saved in the Mongo repository. This simple configuration enables event sourcing in our application.

And that’s it when it comes to configuration. Of course, we have much more possibilities, and we can change behavior by any means, but such a simple configuration allows us to run Axon and use its features.

CQRS implementation with Axon


According to the diagram above, creating commands, passing them to command bus and then creating events and placing them on event bus is not CQRS yet. We have to remember about changing the state of write repository and reading current state from the read database. This is the crucial point of the CQRS pattern.

Configuring this flow should be easy as well. While passing the command to the command gateway, Spring searches methods annotated with @CommandHandler with command type as argument.

@Value
class SubmitApplicationCommand {
   private String appId;
   private String category;
}

@AllArgsConstructor
public class ApplicationService {
   private final CommandGateway commandGateway;

   public CompletableFuture<Void> createForm(String appId) {
       return CompletableFuture.supplyAsync(() -> new SubmitExpertsFormCommand(appId, "Android"))
               .thenCompose(commandGateway::send);
   }
}

Command handler is responsible, among other things, for sending the created event to the event bus. It places an event object to statically imported apply() method from AggregateLifecycle. The event is later dispatched to find expected handlers and thanks to configured event store, all events are saved in DB automatically.

@Value
class ApplicationSubmittedEvent {
   private String appId;
   private String category;
}

@Aggregate
@NoArgsConstructor
public class ApplicationAggregate {
   @AggregateIdentifier
   private String id;

   @CommandHandler
   public ApplicationAggregate(SubmitApplicationCommand command) {
      //some validation
       this.id = command.getAppId;
       apply(new ApplicationSubmittedEvent(command.getAppId(), command.getCategory()));
   }
}

To change the state of the write DB, we need to provide a method annotated with @EventHandler. The application can contain multiple event handlers. Each of them should perform one specific task like sending emails, logging or saving in database.

@RequiredArgsConstructor
@Order(1)
public class ProjectingEventHandler {
   private final IApplicationSubmittedProjection projection;

   @EventHandler
   public CompletableFuture<Void> onApplicationSubmitted(ExpertsFormSubmittedEvent event) {
       return projection.submitApplication(event.getApplicationId(), event.getCategory());
   }
}

If we want to determine the processing order of all event handlers, we can annotate a class with @Order and set a sequence number. submitApplication() method is responsible for making all the necessary changes and saving new data in write DB.

These are all vital points to make our app event sourced with CQRS pattern principles. Of course, these principles can be applied only in some parts of our application depending on business needs. Event sourcing is not suitable for every application or module we are building. It is also worth to be cautious while implementing this pattern because a more complex application can be hard to maintain.

Conclusion

Implementation of CQRS and Event Sourcing is straightforward with Axon framework. More details about advanced configuration can be found on Axon’s website https://docs.axonframework.org/. Besides, Axon is constantly developed and supported. Thanks to that, we are sure that all reported issues will be fixed on a regular basis making our experience with Axon even better.

Now, let's talk about your project!

We don't have one standard offer.
Each project is unique, rest assured that we will approach the next one full of energy and engagement.

LET'S CONNECT