[JAVA] CQRS + Event Sourcing Framework Axon

A quick introduction to CQRS and event sourcing and implementation in Axon

CQRS (Command Query Responsibility Segregation) and event sourcing

Comparison with common CRUD architecture

A typical CRUD architecture interacts with the system in the following ways:

CRUD.png

  1. Reflect the information obtained from the data source in the model and display the UI based on it
  2. User changes information through UI
  3. Reflect changes in the model
  4. Model performs validation and indirect logic
  5. Reflect model changes in the data source

This architecture is simple, versatile and widely accepted. However, in this architecture, the behavior of the domain model cannot be expressed well because it is a data-centric dialogue by sending and receiving DTO.

CQRS and event sourcing

Applying CQRS and event sourcing can take the following form: CQRS.png

By applying CQRS and clearly separating the update model and the reference model, you can convey the user's intent as a command rather than a data-centric interaction. For example, what could only be described as "update user information" in the CRUD architecture can be expressed with a clearer intention, such as issuing a "change user's address" command. In addition, it will be possible to express whether the address change is a correction of a typo or a move. The domain model can express how it behaves by processing commands and generating events. Also, by applying event sourcing, it becomes the current state by saving "what happened (event) itself" instead of saving "state of the result of something happening" like CRUD. In addition to being able to retain the background and reasons for this, the components can be loosely connected via events, making it highly expandable.

Axon

Axon is a framework based on CQRS and event sourcing. The version introduced this time is 2.4.5. The architecture of Axon is shown in the figure below.

detailed-architecture-overview (1).png

The state change for the application starts with Command. Processed by the CommandHandler to change the state of the domain object (Aggregate). And the domain event is generated by the state change of the domain object. Events that occur on domain objects are persisted in the event store through the repository. Events are also dispatched through EventBus, where event handlers update the data source used for queries and send messages to external systems.

Let's see how to implement it in Axon using a simple application that only registers and marks tasks as an example.

Command and Event

Command is an object that expresses the intention for the application and has the data necessary for processing based on that intention. Event is an object that represents what happened in your application. The command and event created by Todo are as follows.

public class CreateToDoItemCommand {

    @TargetAggregateIdentifier
    private final String todoId;
    private final String description;

    public CreateToDoItemCommand(String todoId, String description) {
        this.todoId = todoId;
        this.description = description;
    }

    public String getTodoId() {
        return todoId;
    }

    public String getDescription() {
        return description;
    }
}
public class ToDoItemCreatedEvent {

    private final String todoId;
    private final String description;

    public ToDoItemCreatedEvent(String todoId, String description) {
        this.todoId = todoId;
        this.description = description;
    }

    public String getTodoId() {
        return todoId;
    }

    public String getDescription() {
        return description;
    }
}

@TargetAggregateIdentifier indicates the field (or method) used to identify the target Aggregate instance. Similarly, create a Command and Event to mark the completion.

public class MarkCompletedCommand {

    @TargetAggregateIdentifier
    private final String todoId;

    public MarkCompletedCommand(String todoId) {
        this.todoId = todoId;
    }

    public String getTodoId() {
        return todoId;
    }
}
public class ToDoItemCompletedEvent {

    private final String todoId;

    public ToDoItemCompletedEvent(String todoId) {
        this.todoId = todoId;
    }

    public String getTodoId() {
        return todoId;
    }
}

Domain model

The domain model in Axon behaves as an Aggregate that receives a Command, changes the state, and issues an Event to it. The implementation of ToDoItem that represents ToDo is as follows.

public class ToDoItem extends AbstractAnnotatedAggregateRoot {

    @AggregateIdentifier
    private String id;
    private String description;
    private boolean completed;

    public ToDoItem() {
    }

    @CommandHandler
    public ToDoItem(CreateToDoItemCommand command) {
        apply(new ToDoItemCreatedEvent(command.getTodoId(), command.getDescription()));
    }


    @CommandHandler
    public void markCompleted(MarkCompletedCommand command) {
       apply(new ToDoItemCompletedEvent(id));
     }

    @EventSourcingHandler
    public void on(ToDoItemCreatedEvent event) {
        this.id = event.getTodoId();
        this.desc = event.getDescription();
    }

    @EventSourcingHandler
    public void on(ToDoItemCompletedEvent event) {
        this.completed = true;
    }
}

ʻAbstractAnnotatedAggregateRoot` provides functions such as event persistence, dispatch to EventBus, and initialization of domain object (Aggregate) based on the event stream obtained from the event store.

First, I want to create a new instance of ToDoItem with CreateToDoItemCommand, so @ CommandHandler Create a constructor with. By calling apply () in the constructor, ToDoItemCreatedEvent is issued and persisted in the event store. Also, the generated event is dispatched through EventBus to the event listener interested in ToDoItemCreatedEvent. Similarly, if you add a completion mark, you want to generate ToDoItemCompletedEvent, so create a markCompleted method. When MarkCompletedCommand is issued, markCompleted () is called for the ToDoItem instance where the event stream loaded from the event store is applied and the value is set.

Also, create an event handler with @EventSourcingHandler to initialize the state of the instance when creating a ToDoItem instance.

Event listener

You can easily perform actions on Events by creating an event listener. For example, write the current state of ToDoItem to the reference DB as shown below.

public class ToDoEventListener {
 
    @EventHandler
    public void handle(ToDoItemCreatedEvent event) {
        //Update DB for reference
    }
 
    @EventHandler
    public void handle(ToDoItemCompletedEvent event) {
        //Update DB for reference
    }
}

You can also add a function to notify the completion as follows.

public class ToDoEventNotifyListener {
    @EventHandler
    public void handle(ToDoItemCompletedEvent event) {
        //Notify the completion of ToDo
    }
}

Recommended Posts

CQRS + Event Sourcing Framework Axon
Try using Axon Framework