4. Domain Modeling

In a CQRS-based application, a Domain Model (as defined by Eric Evans and Martin Fowler) can be a very powerful mechanism to harness the complexity involved in the validation and execution of state changes. Although a typical Domain Model has a great number of building blocks, two of them play a major role when applied to CQRS: the Event and the Aggregate.

The following sections will explain the role of these building blocks and how to implement them using the Axon Framework.

4.1. Events

The Axon Framework makes a distinction between three types of events, each with a clear use and type of origin. Regardless of their type, all events must implement the Event interface or one of the more specific sub-types, Domain Events, Application Events and System Events, each described in the sections below.

4.1.1. Domain Events

The most important type of event in any CQRS application is the domain event. It represents an event that occurs inside your domain logic, such as a state change or special notification of a certain state. The latter not being per definition a state change.

In the Axon Framework, all domain events should extend the abstract DomainEvent class. This abstract class keeps track of the aggregate they are generated by, and the sequence number of the event inside the aggregate. This information is important for the Event Sourcing mechanism, as well as for event handlers (see Section 6.2, “Event Listeners”) that need to know the origin of an event.

Although not enforced, it is good practice to make domain events immutable, preferably by making all fields final and by initializing the event within the constructor.

[Note]Note

Although Domain Events technically indicate a state change, you should try to capture the intention of the state in the event, too. A good practice is to use an abstract implementation of a domain event to capture the fact that certain state has changed, and use a concrete sub-implementation of that abstract class that indicates the intention of the change. For example, you could have an abstract AddressChangedEvent, and two implementations ContactMovedEvent and AddressCorrectedEvent that capture the intent of the state change. Some listeners will care about the intent (e.g. to send an address change confirmation email to the customer), while others don't (e.g. database updating event listeners). The latter will listen to events of the abstract type, while the former will listen to the concrete subtypes.

Adding intent to events

Figure 4.1. Adding intent to events


There is a special type of DomainEvent, which has a special meaning: theAggregateDeletedEvent. This event can be extended to indicate that the event indicates a migration to a "deleted" state of the aggregate. Repositories must consider aggregates that have applied such an event as deleted. Loading such an aggregate in again results in an exception.

Snapshot events are instances of DomainEvent with a special intent. They are typically not dispatched via the event bus, but are used to summarize an arbitrary number of events from the past into a single entry. This can drastically improve performance when initializing an aggregate's state from a series of events. See Section 5.4, “Using Snapshot Events” for more information about snapshot events and their use.

4.1.2. Application Events

Application events are events that cannot be categorized as domain events, but do have a significant importance for the application. When using application events, check if the event is actually a domain event that you over looked. Examples of application events are the expiry of a user session, or the notification of an email being successfully send. The usefulness of these events depend on the type of application you are creating.

In the Axon Framework, you can extend the abtract ApplicationEvent class for application events. This class will generate a unique identifier and a time stamp for the current event. Optionally, you can attach an object that acts as the source of the event. This source is loosely attached, which means that if the garbage collector cleans up the source, or when the event is serialized and deserialized, the original source class is not available anymore. Instead, you will have access to the type of source and the value of it's toString() method.

4.1.3. System Events

The third type of event identified by Axon Framework is the System Event. These events typically provide notifications of the status of the system. These events could, for example, indicate that a subsystem is non-responsive or has raised an exception.

All system events extend the abstract SystemEvent class. Upon construction of this event, you may pass an exception, defining the cause of the event, and a source object which is considered the source of the event. This object is loosely referenced from the event.

4.2. Aggregate

An Aggregate is an entity or group of entities that is always kept in a consistent state. The aggregate root is the object on top of the aggregate tree that is responsible for maintaining this consistent state.

[Note]Note

The term "Aggregate" refers to the aggregate as defined by Evans in Domain Driven Design:

A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the Aggregate, designated as the root. A set of consistency rules applies within the Aggregate's boundaries.

A more extensive definition can be found on: http://domaindrivendesign.org/freelinking/Aggregate.

For example, a "Contact" aggregate will contain two entities: contact and address. To keep the entire aggregate in a consistent state, adding an address to a contact should be done via the contact entity. In this case, the Contact entity is the appointed aggregate root.

4.2.1. Basic aggregate implementations

AggregateRoot

In Axon, all aggregate roots must implement the AggregateRoot interface. This interface describes the basic operations needed by the Repository to store and publish the generated domain events. However, Axon Framework provides a number of abstract implementations that help you writing your own aggregates.

[Note]Note

Note that only the aggregate root needs to implement the AggregateRoot interface or implement one of the abstract classes mentioned below. The other entities that are part of the aggregate do not have to implement any interfaces.

VersionedAggregateRoot

The VersionedAggregateRoot interface provides the information needed by repositories to perform optimistic locking. The only method added to the AggregateRoot interface is getLastCommittedEventSequenceNumber, which returns the sequence number of the event that was last committed. See the section called “LockingRepository for more information about the abstract LockingRepository implementation.

AbstractAggregateRoot

The AbstractAggregateRoot is a basic implementation that provides a registerEvent(DomainEvent) method that you can call in your business logic method to have an event added to the list of uncommitted events. The AbstractAggregateRoot will keep track of all uncommitted registered events and make sure they are forwarded to the event bus when the aggregate is saved to a repository.

4.2.2. Event sourcing aggregates

Axon framework provides a few repository implementations that can use event sourcing as storage method for aggregates. These repositories require that aggregates implement the EventSourcedAggregateRoot interface. As with most interfaces in Axon, we also provide one or more abstract implementation to help you on your way.

EventSourcedAggregateRoot

The EventSourcedAggregateRoot defines an extra method, initializeState(), on top of the VersionedAggregateRoot interface. This method initializes an aggregate's state based on an event stream.

AbstractEventSourcedAggregateRoot

The AbstractEventSourcedAggregateRoot implements all methods on the EventSourcedAggregateRoot interface. It defines an abstract handle() method, which you need to implement with the actual logic to apply state changes based on domain events. When you extend the AbstractEventSourcedAggregateRoot, you can register new events using the apply() method. This method will register the event to be committed when the aggregate is saved, and will call the handle() method with the event as parameter.

public class MyAggregateRoot extends AbstractEventSourcedAggregateRoot {

    private String someProperty;

    public MyAggregateRoot() {
        apply(new MyAggregateCreatedEvent());
    }

    public MyAggregateRoot(UUID identifier) {
        super(identifier);
    }

    public void handle(DomainEvent event) {
        if (event instanceof MyAggregateCreatedEvent) {
            // do something with someProperty
        }
        // and more if-else-if logic here
    }
}                

AbstractAnnotatedAggregateRoot

As you see in the example above, the implementation of the handle() method can become quite verbose and hard to read. The AbstractAnnotatedAggregateRoot can help. The AbstractAnnotatedAggregateRoot is a specialization of the AbstractAggregateRoot that provides @EventHandler annotation support to your aggregate. Instead of a single handle() method, you can split the logic in separate methods, with names that you may define yourself. Just annotate the event handler methods with @EventHandler, and the AbstractAnnotatedAggregateRoot will invoke the right method for you.

public class MyAggregateRoot extends AbstractEventSourcedAggregateRoot {
    private String someProperty;

    public MyAggregateRoot() {
        apply(new MyAggregateCreatedEvent());
    }

    public MyAggregateRoot(UUID identifier) {
        super(identifier);
    }

    @EventHandler
    private void handleMyAggregateCreatedEvent(MyAggregateCreatedEvent event) {
        // do something with someProperty
    }
}                

In all circumstances, exactly one event handler method is invoked. The AbstractAnnotatedAggregateRoot will search the most specific method to invoke, in the following order:

  1. On the actual instance level of the class hierarchy (as returned by this.getClass()), all annotated methods are evaluated

  2. If one or more methods are found of which the parameter is of the event type or a super type, the method with the most specific class (the subclass) is chosen and invoked

  3. If no methods are found on this level of the class hierarchy, the super type is evaluated the same way

  4. When the level of the AbstractAnnotatedAggregateRoot is reached, and no suitable event handler is found, an UnhandledEventException is thrown.

Event handler methods may be private, as long as the security settings of the JVM allow the Axon Framework to change the accessibility of the method. This allows you to clearly separate the public API of your aggregate, which exposes the methods that generate events, from the internal logic, which processes the events.

[Tip]Tip

An Aggregate will only contain fields of properties it uses for validation or business logic decisions. That means you will likely have some events that have no direct effect on any fields in the aggregate. In that case you can choose to create a handleOtherEvents(DomainEvent event) method with an empty body. This handler will be called for any event for which there is no specific handler, preventing any exception being thrown. Do consider, however, that doing so may result in unexpected behavior if an event handler for a specific type of event is forgotten.