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.
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.
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 |
---|---|
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 |
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.
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.
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.
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 |
---|---|
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.
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 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. |
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.
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.
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.
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.
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 } }
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:
On the actual instance level of the class hierarchy (as returned by
this.getClass()
), all annotated methods are evaluated
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
If no methods are found on this level of the class hierarchy, the super type is evaluated the same way
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 |
---|---|
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
|