NoteThe 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.”
equals
and hashCode
to ensure good equality comparison with other instances,toString()
method that provides a consistent result (equal identifiers should provide an equal toString() result), andSerializable
.String
, UUID
and the numeric types are always suitable. Do not use primitive types as identifiers, as they don't allow for lazy initialization. Axon may, in some circumstances, falsely assume the default value of a primitive to be the value of the identifier.NoteIt 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 × 10 11 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.
@AggregateIdentifier
annotation. If you use JPA and have JPA annotations on the aggregate, Axon can also use the @Id
annotation provided by JPA.AggregateLifecycle.apply()
method to register events for publication. Unlike the EventBus
, where messages need to be wrapped in an EventMessage, apply()
allows you to pass in the payload object directly.@EventHandler
annotated method. These methods will be invoked when an EventMessage is published (before any external handlers are published).apply
method to publish Events. However, state changes in Event Sourced Aggregates (i.e. any change of a Field value) must be exclusively performed in an @EventSourcingHandler
annotated method. This includes setting the Aggregate Identifier.@EventSourcingHandler
of the very first Event published by the Aggregate. This is usually the creation Event.@EventSourcingHandler
annotated methods are resolved using specific rules. These rules are the same for the @EventHandler
annotated methods, and are thoroughly explained in Annotated Event Handler.NoteEvent 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.Most IDE's have an option to ignore "unused private method" warnings for methods with a specific annotation. Alternatively, you can add an@SuppressWarnings("UnusedDeclaration")
annotation to the method to make sure you don't accidentally delete an Event handler method.
apply()
new events inside an Event Sourcing Handler method. This makes it possible for an Entity B to apply an event in reaction to Entity A doing something. Axon will ignore the apply() invocation when replaying historic events. Do note that, in this case, the Event of the inner apply()
invocation is only published to the Entities after all Entities have received the first Event. If more events need to be published, based on the state of an entity after applying an inner event, use apply(...).andThenApply(...)
AggregateLifecycle.isLive()
method to check whether the aggregate is 'live'. Basically, an aggregate is considered live if it has finished replaying historic events. While replaying these events, isLive() will return false. Using this isLive()
method, you can perform activity that should only be done when handling newly generated events.AggregateLifecycle
introduces the static method createNew()
.AggregateA
defined like this:AggregateB
, like so:AggregateLifecycle#createNew()
method is the type of Aggregate to be created. The second parameter is the factory method - the method to be used in order to instantiate the desired Aggregate.Note Creation of a new Aggregate should be done in a Command Handling function rather than in an Event Handling function (given the usage of Event Sourced Aggregate). Rationale: we do not want to create new Aggregates when we are sourcing a given Aggregate - previously created aggregate will be Event Sourced based on its events. However, if you try to create a new Aggregate while Axon is replaying events, anUnsupportedOperationException
will be thrown.
Note A common misinterpretation of the rule that Aggregates should not expose state is that none of the Entities should contain any property accessor methods. This is not the case. In fact, an Aggregate will probably benefit a lot if the entities within the aggregate expose state to the other entities in that same aggregate. However, it is recommended not to expose the state outside of the Aggregate.
@AggregateMember
. This annotation tells Axon that the annotated field contains a class that should be inspected for Command and Event Handlers.@AggregateMember
annotated fields to its child entities.Note There is a way to filter the entities which would handle an event applied by the Aggregate Root. This can be achieved by usingeventForwardingMode
attribute of@AggregateMember
annotation. By default, an event is propagated to all child entities. An event can be blocked usingForwardNone
event forwarding mode (see listing below).public class MyAggregate {...@AggregateMember(eventForwardingMode = ForwardNone.class)private MyEntity myEntity;...}If you want to forward an event to the entity only in a case when an event message has matching entity identifier useForwardMatchingInstances
event forwarding mode. Entity identifier matching will be done based on specifiedroutingKey
on@AggregateMember
annotation. IfroutingKey
is not specified on@AggregateMember
annotation, matching will be done based onroutingKey
attribute on@EntityId
annotation. IfroutingKey
is not specified on@EntityId
annotation matching will be done based on field name of entity identifier. Let's take a look at example on how to defineForwardMatchingInstances
event forwarding mode with specifying a routing key for the entity identifier:public class MyAggregate {...@AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)private MyEntity myEntity;...}...public class MyEntity {...@EntityId(routingKey = "myEntityId")private String id;...@EventSourcingHandlerpublic void on(MyEvent event) {// handle event}}...public class MyEvent {...private String myEntityId;...}
@AggregateMember
. This annotation may be used on a number of field types:Iterable
(which includes all collections, such as Set
, List
, etc);java.util.Map
@CommandHandler
. The rules for an @CommandHandler
annotated method are the same as for any handler method. However, Commands are not only routed by their payload. Command Messages carry a name, which defaults to the fully qualified class name of the Command object.@CommandHandler
annotated methods allow the following parameter types:Message
or CommandMessage
, if the @CommandHandler
annotation explicitly defined the name of the Command the handler can process. By default, a Command name is the fully qualified class name of the Command's payload.@MetaDataValue
will resolve to the Meta Data value with the key as indicated on the annotation. If required
is false
(default), null
is passed when the meta data value is not present. If required
is true
, the resolver will not match and prevent the method from being invoked when the meta data value is not present.MetaData
will have the entire MetaData
of a CommandMessage
injected.UnitOfWork
get the current Unit of Work injected. This allows command handlers to register actions to be performed at specific stages of the Unit of Work, or gain access to the resources registered with it.Message
, or CommandMessage
will get the complete message, with both the payload and the Meta Data. This is useful if a method needs several meta data fields, or other properties of the wrapping Message.@TargetAggregateIdentifier
. The annotation may be placed on either the field or an accessor method (e.g. a getter).CommandTargetResolver
. This class should return the Aggregate Identifier and expected version (if any) based on a given command.NoteWhen the@CommandHandler
annotation is placed on an Aggregate's constructor, the respective command will create a new instance of that aggregate and add it to the repository. Those commands do not require to target a specific aggregate instance. Therefore, those commands do not require any@TargetAggregateIdentifier
or@TargetAggregateVersion
annotations, nor will a customCommandTargetResolver
be invoked for these commands.When a command creates an aggregate instance, the callback for that command will receive the aggregate identifier when the command executed successfully.
@CommandHandler
annotations are not limited to the aggregate root. Placing all command handlers in the root will sometimes lead to a large number of methods on the aggregate root, while many of them simply forward the invocation to one of the underlying entities. If that is the case, you may place the @CommandHandler
annotation on one of the underlying entities' methods. For Axon to find these annotated methods, the field declaring the entity in the aggregate root must be marked with @AggregateMember
. Note that only the declared type of the annotated field is inspected for Command Handlers. If a field value is null at the time an incoming command arrives for that entity, an exception is thrown.NoteNote that each command must have exactly one handler in the aggregate. This means that you cannot annotate multiple entities (either root nor not) with @CommandHandler, that handle the same command type. In case you need to conditionally route a command to an entity, the parent of these entities should handle the command, and forward it based on the conditions that apply.The runtime type of the field does not have to be exactly the declared type. However, only the declared type of the@AggregateMember
annotated field is inspected for@CommandHandler
methods.
@AggregateMember
. In the latter case, the values of the map are expected to contain the entities, while the key contains a value that is used as their reference.@EntityId
. The property on the command that will be used to find the Entity that the message should be routed to, defaults to the name of the field that was annotated. For example, when annotating the field "myEntityId", the command must define a property with that same name. This means either a getMyEntityId
or a myEntityId()
method must be present. If the name of the field and the routing property differ, you may provide a value explicitly using @EntityId(routingKey = "customRoutingProperty")
.NoteThe field declaration for both the Collection or Map should contain proper generics to allow Axon to identify the type of Entity contained in the Collection or Map. If it is not possible to add the generics in the declaration (e.g. because you're using a custom implementation which already defines generic types), you must specify the type of entity used in theentityType
property on the@AggregateMember
annotation.
@CommandHandler
annotated methods. Unlike in the case of an Aggregate, there is only a single instance of a Command Handler object, which handles all commands of the types it declares in its methods.@CommandHandler
on an Aggregate's constructor. In this case, instead of returning the return value of the method (which is the Aggregate itself), the value of the @AggregateIdentifier
annotated field is returned insteadNoteWhile it's possible to return results from Commands, it should be used sparsely. The intent of the command should never be in getting a value, as that would be an indication the message should be designed as a Query Message instead. A typical example for a Command result is the identifier of a newly created entity.