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.
All events may carry data and meta-data. Typically, the data is added to each event as fields in the event implementation. Meta-data, on the other hand is stored separately. The Auditing interceptor uses this mechanism to attach meta-data to events for auditing purposes. All Axon's implementations of Events allow the subclasses to attach arbitrary information as meta-data.
Note | |
---|---|
In general, you should not base business decisions on information in the meta-data of events. If that is the case, you might have information attached that should really be part of the event's regular data instead. Meta-data is typically used for auditing and tracing. |
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 Event
, which has a special meaning: the
AggregateDeletedEvent
. This is a marker interface that indicates a
migration to a "deleted" state of the aggregate. Repositories must treat aggregates
that have applied such an event as deleted. Hence, loading an aggregate that has an
AggregateDeletedEvent
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, “Snapshotting” 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 overlooked. Examples of application events are the expiry of a user session, or the notification of an email being successfully sent. The usefulness of these events depend on the type of application you are creating.
In the Axon Framework, you can extend the abstract 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 referenced, which means that if the
garbage collector cleans up the source, or when the event is serialized and
de-serialized, 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. As with
application events, the source 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, aggregates are identified by an AggregateIdentifier
. There are
two basic implementations of these identifiers: UUIDAggregateIdentifier
,
which used Java's UUID
to generate random identifiers, and the
StringAggregateIdentifier
, which allows you to choose a
String
which should be used as identifier. You can choose any
identifier type you like, and even create your own, as long as they have a valid String
representation.
Note | |
---|---|
It is considered a good practice to use randomly generated identifiers, as opposed to sequenced ones. Using a sequence drastically reduces scalability of your application, since machines need to keep each other up-to-date of the last used sequence numbers. The chance of collisions with a UUID is very slim (a chance of 10−15, if you generate 8.2 × 1011 UUIDs). Furthermore, be careful when using functional identifiers for aggregates. They have a tendency to change, making it very hard to adapt your application accordingly. |
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
|
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.
The AbstractJpaAggregateRoot
is a JPA-compatible implementation
of the AggregateRoot
interface. It has the annotation necessary to
persist the aggregate's state and reconstruct it from database tables. It uses
the @Version
annotation on one of it's field to perform optimistic
locking on the database level. Similar to the
AbstractAggregateRoot
, the AbstractJpaAggregateRoot
keeps track of uncommitted events, which have been registered using
registerEvent(DomainEvent)
.
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 implementations to
help you on your way.
The interface EventSourcedAggregateRoot
defines an extra method,
initializeState()
, on top of the AggregateRoot
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 apply()
. 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. You need to implement this handle()
method
to apply the state changes represented by that event. Below is a sample
implementation of an aggregate.
public class MyAggregateRoot extends AbstractEventSourcedAggregateRoot { private String someProperty; public MyAggregateRoot() { apply(new MyAggregateCreatedEvent()); } public MyAggregateRoot(AggregateIdentifier 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.
Note | |
---|---|
Note that |
public class MyAggregateRoot extends AbstractAnnotatedAggregateRoot { private String someProperty; public MyAggregateRoot() { apply(new MyAggregateCreatedEvent()); } public MyAggregateRoot(AggregateIdentifier 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 type 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, the event is ignored.
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.
Complex business logic often requires more than what an aggregate with only an aggregate root can provide. In that case, it important that the complexity is spread over a number of entities within the aggregate. When using event sourcing, not only the aggregate root needs to use event to trigger state transitions, but so does each of the entities within that aggregate.
Axon provides support for event sourcing in complex aggregate structures. All
entities other than the aggregate root need to extend from
AbstractEventSourcedEntity
. The
EventSourcedAggregateRoot
implementations provided by Axon
Framework are aware of these entities and will call their event handlers when
needed.
When an entity (including the aggregate root) applies an Event, it is registered
with the Aggregate Root. The aggregate root applies the event locally first. Next,
it will evaluate all its fields for any implementations of
AbstractEventSourcedEntity
and handle the event on them. Each
entity does the same thing to its fields.
To register an Event, the Entity must know about the Aggregate Root. Axon will
automatically register the Aggregate Root with an Entity before applying any Events
to it. This means that Entities (unlike usual in the Aggregate Root) should never
apply an Event in their constructor. Non-Aggregate Root Entities should be created
in an @EventHandler
annotated method in their parent Entity. Axon will
ensure that the Aggregate Root is properly registered in time.
Axon will automatically detect most of the child entities in the fields of an Entity (albeit aggregate root or not). The following Entities are found:
directly referenced in a field;
inside fields containing an Iterable
(which includes all
collections, such as Set
, List
, etc);
inside both they keys and the values of fields containing a
java.util.Map
If you reference an Entity from any other location than the above
mentioned, you can override the getChildEntities()
method. This method
should return a Collection
of entities that should be notified of the
Event. Note that each entity is invoked once for each time it is located in the
returned Collection
.