Repositoryinterface. This interface prescribes three methods:
loadmethods allows you to load aggregates from the repository. The optional
versionparameter is used to detect concurrent modifications (see Advanced conflict detection and resolution).
newInstanceis used to register newly created aggregates in the repository.
delete(identifier)method. Deleting aggregates is done by invoking the
AggregateLifecycle.markDeleted()method from within an aggregate. Deleting an aggregate is a state migration like any other, with the only difference that it is irreversible in many cases. You should create your own meaningful method on your aggregate which sets the aggregate's state to "deleted". This also allows you to register any events that you would like to have published.
GenericJpaRepository. It expects the Aggregate to be a valid JPA Entity. It is configured with an
EntityManagerProviderwhich provides the
EntityManagerto manage the actual persistence, and a class specifying the actual type of Aggregate stored in the Repository. You also pass in the
EventBusto which Events are to be published when the Aggregate invokes the static
LockingRepository. As aggregate wrapper type, it is recommended to use the
AnnotatedAggregate. See the sources of
GenericJpaRepositoryfor an example.
EventSourcingRepositoryimplementation provides the basic functionality needed by any event sourcing repository in the AxonFramework. It depends on an
EventStore(see Event store implementations), which abstracts the actual storage mechanism for the events.
EventSourcingRepositorycan initialize it using the Events it loaded from the Event Store. Axon Framework comes with a number of
AggregateFactoryimplementations that you may use. If they do not suffice, it is very easy to create your own implementation.
GenericAggregateFactoryis a special
AggregateFactoryimplementation that can be used for any type of Event Sourced Aggregate Root. The
GenericAggregateFactorycreates an instance of the Aggregate type the repository manages. The Aggregate class must be non-abstract and declare a default no-arg constructor that does no initialization at all.
SpringPrototypeAggregateFactory. Instead of creating regular instances of using a constructor, it uses the Spring Application Context to instantiate your aggregates. This will also inject any dependencies in your aggregate.
GenericAggregateFactoryjust doesn't deliver what you need. For example, you could have an abstract aggregate type with multiple implementations for different scenarios (e.g.
BackOfficeAccountboth extending an
Account). Instead of creating different repositories for each of the aggregates, you could use a single repository, and configure an AggregateFactory that is aware of the different implementations.
CachingEventSourcingRepositoryprovides a cache from which aggregates can be loaded if available.
EmbeddedEventStore. It delegates actual storage and retrieval of events to an
JpaEventStorageEnginestores events in a JPA-compatible data source. The JPA Event Store stores events in so called entries. These entries contain the serialized form of an event, as well as some fields where meta-data is stored for fast lookup of these entries. To use the
JpaEventStorageEngine, you must have the JPA (
javax.persistence) annotations on your classpath.
META-INF/persistence.xmlfile) to contain the classes
SnapshotEventEntry(both in the
DomainEventEntry(the class used by the
JpaEventStore) with the persistence context.
NoteAxon uses Locking to prevent two threads from accessing the same Aggregate. However, if you have multiple JVMs on the same database, this won't help you. In that case, you'd have to rely on the database to detect conflicts. Concurrent access to the event store will result in a Key Constraint Violation, as the table only allows a single Event for an aggregate with any sequence number. Inserting a second event for an existing aggregate with an existing sequence number will result in an error.The
JpaEventStorageEnginecan detect this error and translate it to a
ConcurrencyException. However, each database system reports this violation differently. If you register your
JpaEventStore, it will try to detect the type of database and figure out which error codes represent a Key Constraint Violation. Alternatively, you may provide a
PersistenceExceptionTranslatorinstance, which can tell if a given exception represents a Key Constraint Violation.If no
PersistenceExceptionTranslatoris provided, exceptions from the database driver are thrown as-is.
EntityManagerProviderimplementation that returns the
EntityManagerinstance for the
EventStorageEngineto use. This also allows for application managed persistence contexts to be used. It is the
EntityManagerProvider's responsibility to provide a correct instance of the
EntityManagerProvideravailable, each for different needs. The
SimpleEntityManagerProvidersimply returns the
EntityManagerinstance which is given to it at construction time. This makes the implementation a simple option for Container Managed Contexts. Alternatively, there is the
ContainerManagedEntityManagerProvider, which returns the default persistence context, and is used by default by the Jpa Event Store.
JpaEventStore, this is what the
EntityManagerProviderimplementation could look like:
SnapshotEventEntryentities. While this will suffice in many cases, you might encounter a situation where the meta-data provided by these entities is not enough. Or you might want to store events of different aggregate types in different tables.
JpaEventStorageEngine. It contains a number of protected methods that you can override to tweak its behavior.
WarningNote that persistence providers, such as Hibernate, use a first-level cache on their
EntityManagerimplementation. Typically, this means that all entities used or returned in queries are attached to the
EntityManager. They are only cleared when the surrounding transaction is committed or an explicit "clear" is performed inside the transaction. This is especially the case when the Queries are executed in the context of a transaction.To work around this issue, make sure to exclusively query for non-entity objects. You can use JPA's "SELECT new SomeClass(parameters) FROM ..." style queries to work around this issue. Alternatively, call
EntityManager.clear()after fetching a batch of events. Failure to do so might result in
OutOfMemoryExceptions when loading large streams of events.
ConnectionProviderto obtain connections. Typically, these connections can be obtained directly from a DataSource. However, Axon will bind these connections to a Unit of Work, so that a single connection is used in a Unit of Work. This ensures that a single transaction is used to store all events, even when multiple Units of Work are nested in the same thread.
NoteSpring users are recommended to use the
SpringDataSourceConnectionProviderto attach a connection from a
DataSourceto an existing transaction.
MongoEventStorageEngine, which uses MongoDB as backing database. It is contained in the Axon Mongo module (Maven artifactId
MongoEventStorageEnginestores each event in a separate document. It is, however, possible to change the
StorageStrategyused. The alternative provided by Axon is the
DocumentPerCommitStorageStrategy, which creates a single document for all Events that have been stored in a single commit (i.e. in the same
SequenceEventStorageEngineis a wrapper around two other Event Storage Engines. When reading, it returns the events from both event storage engines. Appended events are only appended to the second event storage engine. This is useful in cases where two different implementations of Event Storage are used for performance reasons, for example. The first would be a larger, but slower event store, while the second is optimized for quick reading and writing.
FilteringEventStorageEngineallows Events to be filtered based on a predicate. Only Events that match this predicate will be stored. Note that Event Processors that use the Event Store as a source of Events, may not receive these events, as they are not being stored.
EventStorageEngineimplementation that keeps the stored events in memory: the
InMemoryEventStorageEngine. While it probably outperforms any other event store out there, it is not really meant for long-term production use. However, it is very useful in short-lived tools or tests that require an event store.
XStreamSerializer, which uses XStream to serialize Events into XML. XStream is reasonably fast and is more flexible than Java Serialization. Furthermore, the result of XStream serialization is human readable. Quite useful for logging and debugging purposes.
JacksonSerializer, which uses Jackson to serialize Events into JSON. While it produces a more compact serialized form, it does require that classes stick to the conventions (or configuration) required by Jackson.
Serializer, and configuring the Event Store to use that implementation instead of the default.
XStreamSerializer's capability to serialize virtually anything makes it a very decent default, its output isn't always a form that makes it nice to share with other applications. The
JacksonSerializercreates much nicer output, but requires a certain structure in the objects to serialize. This structure is typically present in events, making it a very suitable event serializer.
eventSerializeris configured, Events are serialized using the main serializer that has been configured (which in turn defaults to the XStream serializer).
xand output zero or more new events of revision
x + 1. Moreover, upcasters are processed in a chain, meaning that the output of one upcaster is sent to the input of the next. This allows you to update events in an incremental manner, writing an Upcaster for each new event revision, making them small, isolated, and easy to understand.
NotePerhaps the greatest benefit of upcasting is that it allows you to do non-destructive refactoring, i.e. the complete event history remains intact.
RevisionResolver, configured in the serializer. Axon provides several implementations of the
RevisionResolver, such as the
AnnotationRevisionResolver, which checks for an
@Revisionannotation on the Event payload, a
SerialVersionUIDRevisionResolverthat uses the
serialVersionUIDas defined by Java Serialization API and a
FixedValueRevisionResolver, which always returns a predefined value. The latter is useful when injecting the current application version. This will allow you to see which version of the application generated a specific event.
MavenArtifactRevisionResolverto automatically use the project version. It is initialized using the groupId and artifactId of the project to obtain the version for. Since this only works in JAR files created by Maven, the version cannot always be resolved by an IDE. If a version cannot be resolved,
EventMessagedirectly, but with an
IntermediateEventRepresentationprovides functionality to retrieve all necessary fields to construct an
EventMessage(and thus a upcasted
EventMessagetoo), together with the actual upcast functions. These upcast functions by default only allow the adjustment of the events payload, payload type and additions to the event its metadata. The actual representation of the events in the upcast function may vary based on the event serializer used or the desired form to work with, so the upcast function of the
IntermediateEventRepresentationallows the selection of the expected representation type. The other fields, for example the message/aggregate identifier, aggregate type, timestamp etc. are not adjustable by the
IntermediateEventRepresentation. Adjusting those fields is not the intended work for an Upcaster, hence those options are not provided by the provided
Upcasterinterface for events in the Axon Framework works on a
IntermediateEventRepresentationsand returns a
IntermediateEventRepresentations. The upcasting process thus does not directly return the end result of the introduced upcast functions, but chains every upcasting function from one revision to another together by stacking
IntermediateEventRepresentations. Once this process has taken place and the end result is pulled from them, that is when the actual upcasting function is performed on the serialized event.
Upcasterinterface does not upcast a single event; it requires a
Stream<IntermediateEventRepresentation>and returns one. However, an Upcaster is usually written to adjust a single event out of this stream. More elaborate upcasting set ups are also imaginable, for example from one events to multiple, or an upcaster which pulls state from an earlier event and pushes it in a later one. This section describes the currently provided abstract implementations of Event Upcasters which a user can extend to add its own desired upcast functionality.
SingleEventUpcaster- This is a one to one implementation of an event Upcaster. Extending from this implementation requires one to implement a
doUpcastfunction, which respectively check whether the event at hand is to be upcasted, and if so how it should be upcasted. This is most likely the implementation to extend from, as most event adjustments are based on self contained data and are one to one.
EventMultiUpcaster- This is a one to many implementation of an event Upcaster. It is mostly identical to a
SingleEventUpcaster, with the exception that the
doUpcastfunction returns a
Streaminstead of a single
IntermediateEventRepresentation. As such this upcaster allows you to revert a single event to several events. This might be useful if you for example have figured out you want more fine grained events from a fat event.
ContextAwareSingleEventUpcaster- This is a one to one implementation of an Upcaster, which can store context of events during the process. Next to the
doUpcast, the context aware Upcaster requires one to implement a
buildContextfunction, which is used to instantiate a context which is carried between events going through the upcaster. The
doUpcastfunctions receive the context as a second parameter, next to the
IntermediateEventRepresentation. The context can then be used within the upcasting process to pull fields from earlier events and populate other events. It thus allows you to move a field from one event to a completely different event.
ContextAwareEventMultiUpcaster- This is a one to many implementation of an Upcaster, which can store context of events during the process. This abstract implementation is a combination of the
ContextAwareSingleEventUpcaster, and thus services the goal of keeping context of
IntermediateEventRepresentationsand upcasting one such representation to several. This implementation is useful if you not only want to copy a field from one event to another, but have the requirement to generate several new events in the process.
ContentTypeConverters. It will search for the shortest path from type
y, perform the conversion and pass the converted value into the requested upcaster. For performance reasons, conversion will only be performed if the
canUpcastmethod on the receiving upcaster yields true.
ContentTypeConverters may depend on the type of serializer used. Attempting to convert a
byteto a dom4j
Documentwill not make any sense unless a
Serializerwas used that writes an event as XML. To make sure the
UpcasterChainhas access to the serializer-specific
ContentTypeConverters, you can pass a reference to the serializer to the constructor of the
TipTo achieve the best performance, ensure that all upcasters in the same chain (where one's output is another's input) work on the same content type.
XStreamSerializersupports Dom4J as well as XOM as XML document representations. The
EventCountSnapshotTriggerDefinitionprovides the mechanism to trigger snapshot creation when the number of events needed to load an aggregate exceeds a certain threshold. If the number of events needed to load an aggregate exceeds a certain configurable threshold, the trigger tells a
Snapshotterto create a snapshot for the aggregate.
Snapshottersets the actual snapshotter instance, responsible for creating and storing the actual snapshot event;
Triggersets the threshold at which to trigger snapshot creation;
Snapshotterinterface declares a single method:
scheduleSnapshot(), which takes the aggregate's type and identifier as parameters.
AggregateSnapshotter, which creates and stores
AggregateSnapshotinstances. This is a special type of snapshot, since it contains the actual aggregate instance within it. The repositories provided by Axon are aware of this type of snapshot, and will extract the aggregate from it, instead of instantiating a new one. All events loaded after the snapshot events are streamed to the extracted aggregate instance.
NoteDo make sure that the
Serializerinstance you use (which defaults to the
XStreamSerializer) is capable of serializing your aggregate. The
XStreamSerializerrequires you to use either a Hotspot JVM, or your aggregate must either have an accessible default constructor or implement the
AbstractSnapshotterprovides a basic set of properties that allow you to tweak the way snapshots are created:
EventStoresets the event store that is used to load past events and store the snapshots. This event store must implement the
Executorsets the executor, such as a
ThreadPoolExecutorthat will provide the thread to process actual snapshot creation. By default, snapshots are created in the thread that calls the
scheduleSnapshot()method, which is generally not recommended for production.
AggregateSnapshotterprovides one more property:
AggregateFactoriesis the property that allows you to set the factories that will create instances of your aggregates. Configuring multiple aggregate factories allows you to use a single Snapshotter to create snapshots for a variety of aggregate types. The
EventSourcingRepositoryimplementations provide access to the
AggregateFactorythey use. This can be used to configure the same aggregate factories in the Snapshotter as the ones used in the repositories.
NoteIf you use an executor that executes snapshot creation in another thread, make sure you configure the correct transaction management for your underlying event store, if necessary.Spring users can use the
SpringAggregateSnapshotter, which will automatically look up the right
AggregateFactoryfrom the Application Context when a snapshot needs to be created.
NoteNormally, you can archive all events once they are part of a snapshot event. Snapshotted events will never be read in again by the event store in regular operational scenario's. However, if you want to be able to reconstruct aggregate state prior to the moment the snapshot was created, you must keep the events up to that date.
AggregateSnapshot, which stores an entire aggregate as a snapshot. The motivation is simple: your aggregate should only contain the state relevant to take business decisions. This is exactly the information you want captured in a snapshot. All Event Sourcing Repositories provided by Axon recognize the
AggregateSnapshot, and will extract the aggregate from it. Beware that using this snapshot event requires that the event serialization mechanism needs to be able to serialize the aggregate.
@EventHandler), you can annotate a method that initializes full aggregate state based on a snapshot event. The code sample below shows how snapshot events are treated like any other domain event within the aggregate.
AggregateSnapshot. This type of snapshot event contains the actual aggregate. The aggregate factory recognizes this type of event and extracts the aggregate from the snapshot. Then, all other events are re-applied to the extracted snapshot. That means aggregates never need to be able to deal with
@CommandHandlermethod of your aggregate. This interface provides
detectConflictsmethods that allow you to define the types of events that are considered a conflict when executing that specific type of command.
NoteNote that the
ConflictResolverwill only contain any potentially conflicting events if the Aggregate was loaded with an expected version. Use
@TargetAggregateVersionon a field of a command to indicate the expected version of the Aggregate.
detectConflictsallows you to define the exception to throw). If none are found, processing continues as normal.
detectConflictsare made, and there are potentially conflicting events, the
@CommandHandlerwill fail. This may be the case when an expected version is provided, but no
ConflictResolveris available in the parameters of the