State among EntitiesA 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, is is recommended not to expose the state outside of the aggregate.
GiftCardTransactionentity. The snippet above shows two important concepts of multi-entity aggregates:
@AggregateMember. This annotation tells Axon that the annotated field contains a class that should be inspected for message handlers. This example shows the annotation on an implementation of
Iterable, but it can also be placed on a single Object or a
Map. In the latter case, the values of the
Mapare expected to contain the entities, while the key contains a value that is used as their reference. Note that this annotation can be placed on a field and a method.
@EntityIdannotation specifying the identifying field of an Entity. Required to be able to route a command (or event) message to the correct entity instance. The property on the payload that will be used to find the entity that the message should be routed to, defaults to the name of the
@EntityIdannotated field. For example, when annotating the field
transactionId, the command must define a property with that same name, which means either a
getTransactionId()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"). This annotation is mandatory on the Entity implementation if it will be part of a
Mapof child entities. Note that this annotation can be placed on a field and a method.
Defining the Entity typeThe field declaration for both the
Mapshould 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 entity type by specifying the
typefield in the
@AggregateMember(type = GiftCardTransaction.class).
@CommandHandlerannotations 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
@CommandHandlerannotation 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
Mapof child entities and none entity can be found which matches the routing key of the command, Axon throws an
IllegalStateExceptionas apparently the aggregate is not capable of processing the command at that point in time.
Command Handler considerationsNote that each command must have exactly one handler in the aggregate. This means that you cannot annotate multiple entities (either root nor not) with
@CommandHandlerwhich 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
@AggregateMemberannotated field is inspected for
@AggregateMemberannotated field to all its containing child entities:
ForwardMatchingInstancesan Event Message will only be forwarded if it contains a field/getter which matches the name of the
@EntityIdannotated field on the entity. This routing behaviour can be further specified with the
routingKeyfield on the
@EntityIdannotation, mirroring that of routing commands in entities. Other forwarding modes which can be used are
ForwardAll(the default) and
ForwardNone, which respectively forward all events to all entities or no events at all.