@SagaEventHandlermethods. Unlike regular Event Handlers, multiple instances of a Saga may exist at any time. Sagas are managed by a single Processor (Tracking or Subscribing), which is dedicated to dealing with Events for that specific Saga type.
@SagaEventHandler. If a specific Event signifies the start of a transaction, add another annotation to that same method:
@StartSaga. This annotation will create a new saga and invoke its event handler method when a matching Event is published.
forceNewproperty on the
@EndSaga. The Saga's Life Cycle will be ended after the invocation of the handler. Alternatively, you can call
SagaLifecycle.end()from inside the Saga to end the life cycle. This allows you to conditionally end the Saga.
AssociationValueconsists of a key and a value. The key represents the type of identifier used, for example "orderId" or "order". The value represents the corresponding value, "1" or "2" in the previous example.
@SagaEventHandlerannotated methods are evaluated is identical to that of
@EventHandlermethods (see Annotated Event Handler). A method matches if the parameters of the handler method match the incoming Event, and if the saga has an association with the property defined on the handler method.
@SagaEventHandlerannotation has two attributes, of which
associationPropertyis the most important one. This is the name of the property on the incoming Event that should be used to find associated Sagas. The key of the association value is the name of the property. The value is the value returned by property's getter method.
String getOrderId()", which returns "123". If a method accepting this Event is annotated with
@SagaEventHandler(associationProperty="orderId"), this Event is routed to all Sagas that have been associated with an
AssociationValuewith key "orderId" and value "123". This may either be exactly one, more than one, or even none at all.
@SagaEventHandlerannotation. It would then become
@StartSagaannotated Event Handler, it is automatically associated with the property identified in the
@SagaEventHandlermethod. Any other association can be created using the
SagaLifecycle.associateWith(String key, String/Number value)method. Use the
SagaLifecycle.removeAssociationWith(String key, String/Number value)method to remove a specific association.
NoteThe API to associate domain concepts within a Saga intentionally only allows a
Numberas the identifying value, since a
Stringrepresentation of the identifier is required for the association value entry which is stored. Using simple identifier values in the API with a straightforward
Stringrepresentation is by design, as a
Stringcolumn entry in the database makes the comparison between database engines simpler. It is thus intentionally that there is no
associateWith(String, Object)for example, as the result of an
Object#toString()call might provide unwieldy identifiers.
@StartSaga. The Saga is responsible for creating an Invoice for that Order, and tell Shipping to create a Shipment for it. Once both the Shipment have arrived and the Invoice has been paid, the transaction is completed and the Saga is closed.
EventSchedulerto schedule an Event for publication. In the example of an Invoice, you'd expect that invoice to be paid within 30 days. A Saga would, after sending the
CreateInvoiceCommand, schedule an
InvoicePaymentDeadlineExpiredEventto be published in 30 days. The EventScheduler returns a
ScheduleTokenafter scheduling an Event. This token can be used to cancel the schedule, for example when a payment of an Invoice has been received.
EventSchedulerimplementations: a pure Java one and one using Quartz 2 as a backing scheduling mechanism.
ScheduledExecutorServiceto schedule Event publication. Although the timing of this scheduler is very reliable, it is a pure in-memory implementation. Once the JVM is shut down, all schedules are lost. This makes this implementation unsuitable for long-term schedules.
SimpleEventSchedulerneeds to be configured with an
SchedulingExecutorService(see the static methods on the
java.util.concurrent.Executorsclass for helper methods).
QuartzEventScheduleris a more reliable and enterprise-worthy implementation. Using Quartz as underlying scheduling mechanism, it provides more powerful features, such as persistence, clustering and misfire management. This means Event publication is guaranteed. It might be a little late, but it will be published.
EventBus. Optionally, you may set the name of the group that Quartz jobs are scheduled in, which defaults to "AxonFramework-Events".
EventScheduler. To manage transactions on these threads, you can configure a
UnitOfWorkFactorythat creates a Transaction Bound Unit of Work.
NoteSpring users can use the
SimpleEventSchedulerFactoryBeanfor easier configuration. It allows you to set the PlatformTransactionManager directly.
ResourceInjector. It is used by the
SagaRepositoryto inject resources into a Saga. Axon provides a
SpringResourceInjector, which injects annotated fields and methods with Resources from the Application Context, and a
SimpleResourceInjector, which injects resources that have been registered with it into
@Injectannotated methods and fields.
TipSince resources should not be persisted with the Saga, make sure to add the
transientkeyword to those fields. This will prevent the serialization mechanism to attempt to write the contents of these fields to the repository. The repository will automatically re-inject the required resources after a Saga has been deserialized.
SimpleResourceInjectorallows for a pre-specified collection of resources to be injected. It scans the (setter) methods and fields of a Saga to find ones that are annotated with
ConfigurationResourceInjector. It will inject any resource available in the Configuration. Components like the
CommandGatewayare available by default, but you can also register your own components using
SpringResourceInjectoruses Spring's dependency injection mechanism to inject resources into an aggregate. This means you can use setter injection or direct field injection if you require. The method or field to be injected needs to be annotated in order for Spring to recognize it as a dependency, for example with
AnnotatedSagaManager, which is provided to an Event Processor to perform the actual invocation of handlers. It is initialized using the type of the Saga to manage, as well as a SagaRepository where Sagas of that type can be stored and retrieved. A single
AnnotatedSagaManagercan only manage a single Saga type.
SagaStoreimplementation to use. The
SagaStoreis the mechanism that 'physically' stores the Saga instances somewhere. The
AnnotatedSagaRepository(the default) uses the
SagaStoreto store and retrieve Saga instances as they are required.
SagaRepositoryis responsible for storing and retrieving Sagas, for use by the
SagaManager. It is capable of retrieving specific Saga instances by their identifier as well as by their Association Values.
AnnotatedSagaRepositoryimplementation, which allows the lookup of Saga instances while guaranteeing that only a single instance of the Saga is accessed at the same time. It uses a
SagaStoreto perform the actual persistence of Saga instances.
CachingSagaStorewhich wraps another implementation to add caching behavior. Note that the
CachingSagaStoreis a write-through cache, which means save operations are always immediately forwarded to the backing Store, to ensure data safety.
JpaSagaStoreuses JPA to store the state and Association Values of Sagas. Sagas themselves do not need any JPA annotations; Axon will serialize the sagas using a
Serializer(similar to Event serialization, you can choose between an
JavaSerializer, which can be set by configuring the 'Default
Serializer' in your application. For more detail, check Serializers).
JpaSagaStoreis configured with an
EntityManagerProvider, which provides access to an
EntityManagerinstance to use. This abstraction allows for the use of both application managed and container managed
EntityManagers. Optionally, you can define the serializer to serialize the Saga instances with. Axon defaults to the
JdbcSagaStoreuses plain JDBC to store stage instances and their association values. Similar to the
JpaSagaStore, Saga instances don't need to be aware of how they are stored. They are serialized using a Serializer.
JdbcSagaStoreis initialized with either a
ConnectionProvider. While not required, when initializing with a
ConnectionProvider, it is recommended to wrap the implementation in a
UnitOfWorkAwareConnectionProviderWrapper. It will check the current Unit of Work for an already open database connection, to ensure that all activity within a unit of work is done on a single connection.
SagaSqlSchemais an interface that defines all the operations the repository needs to perform on the underlying database. It allows you to customize the SQL statement executed for each one of them. The default is the
GenericSagaSqlSchema. Other implementations available are
MongoSagaStorestores the Saga instances and their associations in a MongoDB database. The
MongoSagaStorestores all sagas in a single Collection in a MongoDB database. Per Saga instance, a single document is created.
MongoSagaStorealso ensures that at any time, only a single Saga instance exists for any unique Saga in a single JVM. This ensures that no state changes are lost due to concurrency issues.
MongoSagaStoreis initialized using a
MongoTemplateand optionally a
MongoTemplateprovides a reference to the collection to store the Sagas in. Axon provides the
DefaultMongoTemplate, which takes the
MongoClientinstance as well as the database name and name of the collection to store the sagas in. The database name and collection name may be omitted. In that case, they default to "axonframework" and "sagas", respectively.
CachingSagaStoreimplementation. It is a
SagaStorethat wraps another one, which does the actual storage. When loading Sagas or Association Values, the
CachingSagaStorewill first consult its caches, before delegating to the wrapped repository. When storing information, all calls are always delegated, to ensure that the backing storage always has a consistent view on the Saga's state.
CachingSagaStore. The constructor of the
CachingSagaStoretakes three parameters: the repository to wrap and the caches to use for the Association Values and Saga instances, respectively. The latter two arguments may refer to the same cache, or to different ones. This depends on the eviction requirements of your specific application.