Class AbstractSagaManager<T>
- All Implemented Interfaces:
DescribableComponent,MessageHandler,EventHandler,EventHandlingComponent,ReplayStatusChangedHandler,ResetHandler,ScopeAware
- Direct Known Subclasses:
AnnotatedSagaManager
- Since:
- 0.7
- Author:
- Allard Buijze
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic classAbstract Builder class to instantiateAbstractSagaManagerimplementations. -
Constructor Summary
ConstructorsModifierConstructorDescriptionprotectedAbstractSagaManager(AbstractSagaManager.Builder<T> builder) Instantiate aAbstractSagaManagerbased on the fields contained in theAbstractSagaManager.Builder. -
Method Summary
Modifier and TypeMethodDescriptionprotected abstract booleancanHandle(EventMessage event, ProcessingContext context) Indicates whether a Saga of the givensagaTypehas a handler for the givenevent.booleancanResolve(ScopeDescriptor scopeDescription) Check whether this implementation can resolve aScopeobject based on the providedscopeDescription.protected StringcreateSagaIdentifier(Segment segment) Creates a Saga identifier that will cause a Saga instance to be considered part of the givensegment.voiddescribeTo(ComponentDescriptor descriptor) Describe the properties ofthis DescribableComponentwith the givendescriptor.protected abstract Set<AssociationValue> extractAssociationValues(EventMessage event, ProcessingContext context) Extracts the AssociationValues from the giveneventas relevant for a Saga of givensagaType.protected abstract SagaInitializationPolicygetSagaCreationPolicy(EventMessage event, ProcessingContext context) Returns the Saga Initialization Policy for a Saga of the givensagaTypeandevent.Returns the class of Saga managed by this SagaManagerhandle(EventMessage rawEvent, ProcessingContext context) Handles the giveneventwithin the givencontext.handle(ResetContext resetContext, ProcessingContext context) Handles the givenResetContextmessage, performing any necessary reset operations.protected booleanmatchesSegment(Segment segment, String sagaId) Checks whether the givensagaIdmatches with the givensegment.voidsend(Message message, ProcessingContext context, ScopeDescriptor scopeDescription) sequenceIdentifierFor(EventMessage event, ProcessingContext context) Returns the sequence identifier for the givenevent.abstract Set<QualifiedName> All supportedevents, referenced through aQualifiedName.booleanIndicates whether this component supports reset operations.protected abstract EventMessagewithHandlerPayload(EventMessage event, ProcessingContext context) Returns the giveneventwith its payload converted to the type the Saga's handler declares for it.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface org.axonframework.messaging.eventhandling.EventHandlingComponent
handle, supports, unwrap
-
Constructor Details
-
AbstractSagaManager
Instantiate aAbstractSagaManagerbased on the fields contained in theAbstractSagaManager.Builder.Will assert that the
sagaType,sagaFactoryandSagaRepositoryare notnull, and will throw anAxonConfigurationExceptionif any of them isnull.- Parameters:
builder- theAbstractSagaManager.Builderused to instantiate aAbstractSagaManagerinstance
-
-
Method Details
-
handle
Handles the giveneventwithin the givencontext.The result of handling is an
empty stream.As an
EventHandlingComponent, this method does not receive theSegmentto filter Saga instances with directly. Instead, theEventProcessorattaches theSegmentit is currently processing to the givencontextas a resource, retrievable throughSegment.fromContext(org.axonframework.messaging.core.Context). When absent, for example when this method is invoked outside a segmentedEventProcessor, theSegment.ROOT_SEGMENTis assumed, matching every Saga instance.Two distinct questions decide what happens here.
canHandle(EventMessage, ProcessingContext)asks whether the Saga type declares a handler for this event at all, andSaga.canHandle(EventMessage, ProcessingContext)asks whether an individual instance holds theAssociationValueits handler resolves from the event. Only the second decides whether a Saga counts as having taken the event, which is what aSagaCreationPolicy.IF_NONE_FOUNDpolicy consults before starting a new instance.- Specified by:
handlein interfaceEventHandler- Parameters:
rawEvent- The event to handle.context- The context to the giveneventis handled in.- Returns:
- An
empty streamcontaining nothing.
-
createSagaIdentifier
Creates a Saga identifier that will cause a Saga instance to be considered part of the givensegment.- Parameters:
segment- The segment the identifier must match with- Returns:
- an identifier for a newly created Saga
- Implementation Requirements:
- This implementation will repeatedly generate identifier using the
IdentifierFactory, until one is returned that matches the given segment. SeematchesSegment(Segment, String).
-
matchesSegment
Checks whether the givensagaIdmatches with the givensegment.For any complete set of segments, exactly one segment matches with any value.
- Parameters:
segment- The segment to validate the identifier forsagaId- The identifier to test- Returns:
trueif the identifier matches the segment, otherwisefalse- Implementation Requirements:
- This implementation uses the
Segment.matches(Object)to match against the Saga identifier
-
getSagaCreationPolicy
protected abstract SagaInitializationPolicy getSagaCreationPolicy(EventMessage event, ProcessingContext context) Returns the Saga Initialization Policy for a Saga of the givensagaTypeandevent. This policy provides the conditions to create new Saga instance, as well as the initial association of that saga.- Parameters:
event- The Event that is being dispatched to Saga instances.context- TheProcessingContextin which the event is being processed.- Returns:
- The initialization policy for the Saga.
-
extractAssociationValues
protected abstract Set<AssociationValue> extractAssociationValues(EventMessage event, ProcessingContext context) Extracts the AssociationValues from the giveneventas relevant for a Saga of givensagaType. A single event may be associated with multiple values.- Parameters:
event- The event containing the association information.context- TheProcessingContextin which the event is being processed.- Returns:
- The AssociationValues indicating which Sagas should handle given event.
-
withHandlerPayload
Returns the giveneventwith its payload converted to the type the Saga's handler declares for it.Events read from an event store carry a serialized payload. Handler matching and association resolution work on the payload's runtime type, so without this step every such event is ignored. Events whose payload already has the handler's type, and events no handler is declared for, are returned unchanged.
- Parameters:
event- the event to convertcontext- theProcessingContextproviding theEventConverter- Returns:
- the event with a converted payload, or the given event
-
canHandle
Indicates whether a Saga of the givensagaTypehas a handler for the givenevent.This check is independent of the
Message.type(): Sagas resolve their handlers by inspecting thepayload'sruntime type, the same way a@SagaEventHandlermethod is matched, since the message name carried by aMessageTypeis not guaranteed to correlate with the payload's class. That makes it stricter thanEventHandlingComponent.supports(QualifiedName), which theEventProcessorconsults before it delivers an event, so this method is asked again on the way in.This is a question about the Saga type, not about any one instance. For the instance-level question, see
Saga.canHandle(EventMessage, ProcessingContext).- Parameters:
event- The event to check for a handler.context- TheProcessingContextin which the event is being processed.- Returns:
trueif a Saga of the givensagaTypehas a handler for the givenevent,falseotherwise.
-
supportedEvents
Description copied from interface:EventHandlingComponentAll supportedevents, referenced through aQualifiedName.- Specified by:
supportedEventsin interfaceEventHandlingComponent- Returns:
- All supported
events, referenced through aQualifiedName.
-
getSagaType
Returns the class of Saga managed by this SagaManager- Returns:
- the managed saga type
-
sequenceIdentifierFor
Returns the sequence identifier for the givenevent. When two events have the same sequence identifier (as defined by their equals method), they will be executed sequentially. Important: AllEventHandlersfor the sameQualifiedNamewithin a singleEventHandlingComponentmust return the same sequence identifier for a given event. Mixing different sequence identifiers within the scope of a singleEventHandlingComponentis not supported and may lead to undefined behavior.Always the
SequencingPolicy.BROADCASTsentinel, so everySegmentof anEventProcessoris offered every event this Saga type handles. A Saga manager is never segment-routed by the event: whichSegmentowns a Saga follows from the Saga's identifier, which is only known after the association lookup inhandle(EventMessage, ProcessingContext), and that lookup needs aSagaRepositoryand a unit of work that do not exist while an event is merely being scheduled. Axon Framework 4 reached the same arrangement from the other side, by answering its own admission check without consulting theSegmentat all.Deriving the identifier from the event instead would route each event to a single
Segment, and thatSegmentneed not be the one owning the Saga. A Saga that associates with a second value is enough to break it: the follow-up event carrying that value hashes elsewhere, the owningSegmentis never offered it, and theSegmentthat is offered it filters the Saga out again by identifier. The event is dropped with nothing logged.Broadcasting does not mean a Saga handles an event more than once. A
TokenStoreclaim makes aSegmentlive on exactly one node,matchesSegment(Segment, String)lets only theSegmentowning the Saga identifier invoke it, and creation is claimed by the singleSegmentmatching the initialAssociationValue. Two limits are inherited from Axon Framework 4 rather than introduced here: a subscribingEventProcessorhas no segments and no token store, so every node falls back toSegment.ROOT_SEGMENTand starts its own Saga; and ais keyed on the Saga identifier alone, with no uniqueness constraint over association values, so segment ownership is the only guard rather than a last line of defence at the storage layer.invalid reference
SagaStore- Specified by:
sequenceIdentifierForin interfaceEventHandlingComponent- Parameters:
event- The event for which to get the sequencing identifier.context- The processing context in which the event is being handled.- Returns:
- A sequence identifier for the given event.
-
supportsReset
public boolean supportsReset()Indicates whether this component supports reset operations.When
true, this component can participate in replay operations and itsEventHandlingComponent.handle(ResetContext, ProcessingContext)method will be called before replay begins.By default, reset is supported.
Always
false. Replaying events into Sagas would rerun their side effects, which is why Axon Framework 4 refused a reset as well.- Specified by:
supportsResetin interfaceEventHandlingComponent- Returns:
trueif this component supports reset operations,falseotherwise.
-
handle
Handles the givenResetContextmessage, performing any necessary reset operations.This method is invoked before the processor begins replaying events. Handlers typically use this opportunity to clean up state that will be rebuilt during replay.
If this method completes exceptionally, the reset operation will be aborted and no replay will occur.
Throws a
ResetNotSupportedExceptionrather than returning a failedMessageStream, which the declared return type would otherwise invite. That is deliberate: anEventProcessorgates both its reset handling and itsresetTokenscall onsupportsReset(), which isfalsehere, so this method is unreachable through one and only a direct caller can arrive at it. A caller reaching past the gate has made a programming error, and Axon Framework 4 threw for it too.- Specified by:
handlein interfaceEventHandlingComponent- Specified by:
handlein interfaceResetHandler- Parameters:
resetContext- The reset context message containing reset information and optional payload.context- The processing context for this operation.- Returns:
- An empty message stream after handling completes successfully.
- Throws:
ResetNotSupportedException- always, since Sagas cannot replay their side effects
-
send
public void send(Message message, ProcessingContext context, ScopeDescriptor scopeDescription) throws Exception Send aMessageto aScopewhich is described by the givenscopeDescription.A deadline message whose stored payload type resolves to no class is sent with an
UnknownDeadlinePayload. Its delivery logs a warning naming the deadline, the stored type and the scope, so that such a deadline is not lost unnoticed.- Specified by:
sendin interfaceScopeAware- Parameters:
message- TheMessageto be sent to aScope.context- TheProcessingContextin which themessageis being processed.scopeDescription- ADextendingScopeDescriptor, describing theScopeto send the givenmessageto.- Throws:
Exception- if sending themessagefailed. Might occur if the message handling process throws an exception.
-
canResolve
Description copied from interface:ScopeAwareCheck whether this implementation can resolve aScopeobject based on the providedscopeDescription. Will returntruein case it should be able to resolve the Scope andfalseif it cannot.- Specified by:
canResolvein interfaceScopeAware- Parameters:
scopeDescription- aScopeDescriptordescribing theScopeto be resolved- Returns:
truein case it should be able to resolve the Scope andfalseif it cannot
-
describeTo
Description copied from interface:DescribableComponentDescribe the properties ofthis DescribableComponentwith the givendescriptor.Components should call the appropriate
describePropertymethods on the descriptor to register their properties. The descriptor is responsible for determining how these properties are formatted and structured in the final output.Best Practices: As a general rule, all relevant fields of a
DescribableComponentimplementation should be described in this method. However, developers have discretion to include only the fields that make sense in the context. Not every field may be meaningful for description purposes, especially internal implementation details. Furthermore, components might want to expose different information based on their current state. The final decision on what properties to include lies with the person implementing thedescribeTomethod, who should focus on providing information that is useful for understanding the component's configuration and state.Example implementation:
public void describeTo(ComponentDescriptor descriptor) { descriptor.describeProperty("name", this.name); descriptor.describeProperty("enabled", this.enabled); descriptor.describeProperty("configuration", this.configuration); // A nested component descriptor.describeProperty("handlers", this.eventHandlers); // A collection }- Specified by:
describeToin interfaceDescribableComponent- Parameters:
descriptor- The component descriptor to describethis DescribableComponentn its properties in.
-