@SagaEventHandler
In the previous chapter, we explored SagaState, the foundation for managing a saga's state. This chapter dives into @SagaEventHandler, a crucial annotation for defining methods that react to events within a saga instance.
Understanding @SagaEventHandler
@SagaEventHandlerThe @SagaEventHandler annotation marks methods inside a saga class that handle specific events. These methods become event listeners, responding to relevant domain events published within the system.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Handler
public @interface SagaEventHandler {
/**
* Initializes the annotated method.
*
* @return true if the method is an initialization handler, false otherwise.
*/
boolean init() default false;
/**
* Retrieves the name of the property used to associate events with a Saga instance.
*
* @return The name of the association property as a {@code String}.
*/
String associationProperty();
public int retry() default -1;
public int retryDelay() default 1000;
}Here's a breakdown of the key aspects of @SagaEventHandler:
Event Listeners: Methods annotated with
@SagaEventHandleract as event listeners for the saga instance. They are invoked whenever a matching event is published.State Management: Sagas leverage
@SagaEventHandlermethods to process events, update their state (SagaState), and potentially trigger actions based on the event data and current saga state.init(Optional): This boolean flag indicates whether the method is an initialization handler. When set totrue(default isfalse), the method is called when the saga is first created in response to a starting event.associationProperty(Mandatory): This attribute specifies the name of the property within the handled event that should be used to associate the event with a saga instance. The saga framework uses this property to link incoming events with the appropriate saga instances.
Optional Retry Functionality:
public int retry() default -1;: This attribute empowers you to define the number of retries the framework should attempt if an exception occurs while executing the event handler. By default, the retry count is set to-1, indicating no specific limit on retries.public int retryDelay() default 1000;: This attribute allows you to specify the delay (in milliseconds) between each retry attempt. The default delay is1000milliseconds (1 second). These optional features enhance the robustness of your event handling logic by enabling automatic retries in case of transient errors.
Handling Persistent Errors: Dead Event Queues (DEQs)
While retries can help overcome temporary hiccups, there may be situations where event handling consistently fails even after retry attempts. To prevent such events from getting stuck in limbo, the Evento Framework utilizes Dead Event Queues (DEQs).
After exceeding the configured retry limit, the event is automatically routed to a dedicated DLQ.
DLQs act as a safety net, storing these unprocessed events for potential future intervention.
By employing DLQs, Evento ensures efficient processing of most events while providing a mechanism to handle persistent failures and prevent data loss.
Requirements for @SagaEventHandler Methods
@SagaEventHandler MethodsThere are two mandatory requirements for methods annotated with @SagaEventHandler:
First Argument as Event: The first argument of the method must be the type of the event it handles. This allows the method to access the event data for processing.
associationPropertySetting: You must define theassociationPropertyattribute within the annotation. This property name should correspond to a field within the handled event that uniquely identifies the saga instance the event relates to.
Utilizing SagaState and Optional Parameters
SagaState and Optional ParametersSagaState:@SagaEventHandlermethods can leverage theSagaStateobject (often the return type) to manage the saga's state throughout its lifecycle. They might update the state based on the event data and the current saga state.Optional Parameters:
@SagaEventHandlermethods can also receive additional optional parameters:CommandGatewayandQueryGateway: These gateways allow interacting with the domain by sending commands or executing queries within the saga logic.EventMessage<?>: Provides access to the entire event message object.Instant: Offers the timestamp of the event occurrence.Metadata: Grants access to any metadata associated with the event.
Example: DemoSaga Event Handlers
DemoSaga Event Handlers@SagaEventHandler(init = true, associationProperty = "demoId")
public DemoSagaState on(DemoCreatedEvent event,
CommandGateway commandGateway,
QueryGateway queryGateway,
EventMessage<?> message) {
Utils.logMethodFlow(this, "on", event, "BEGIN");
DemoSagaState demoSagaState = new DemoSagaState();
demoSagaState.setAssociation("demoId", event.getDemoId());
demoSagaState.setLastValue(event.getValue());
Utils.logMethodFlow(this, "on", event, "END");
return demoSagaState;
}
@SagaEventHandler(associationProperty = "demoId")
public DemoSagaState on(DemoUpdatedEvent event,
DemoSagaState demoSagaState,
CommandGateway commandGateway,
QueryGateway queryGateway,
EventMessage<?> message) throws ExecutionException, InterruptedException {
Utils.logMethodFlow(this, "on", event, "BEGIN");
if (event.getValue() == 12)
{
var demo = queryGateway.query(new DemoRichViewFindByIdQuery(event.getDemoId())).get();
System.out.println(jump(commandGateway, demo.getData().toString()));
}
demoSagaState.setLastValue(event.getValue());
Utils.logMethodFlow(this, "on", event, "END");
return demoSagaState;
}
@SagaEventHandler(associationProperty = "demoId")
public DemoSagaState on(DemoDeletedEvent event,
DemoSagaState demoSagaState,
CommandGateway commandGateway,
QueryGateway queryGateway,
EventMessage<?> message) throws ExecutionException, InterruptedException {
Utils.logMethodFlow(this, "on", event, "BEGIN");
System.out.println(this.getClass() + " - on(DemoDeletedEvent)");
var demo = queryGateway.query(new DemoRichViewFindByIdQuery(event.getDemoId())).get();
var resp = commandGateway.send(new NotificationSendSilentCommand("lol" + demo.getData().toString())).get();
System.out.println(resp);
demoSagaState.setEnded(true);
Utils.logMethodFlow(this, "on", event, "END");
return demoSagaState;
}The provided DemoSaga class showcases several @SagaEventHandler methods:
on(DemoCreatedEvent): This method is marked withinit=true, indicating it's the initialization handler for the saga. It creates a newDemoSagaState, sets the association based on thedemoIdfrom the event, and returns the state object.on(DemoUpdatedEvent): This method handlesDemoUpdatedEvent. It checks a condition based on the event's value and potentially interacts with the domain using gateways. It updates thelastValueproperty in theSagaStateand returns the updated state.on(DemoDeletedEvent): This method handlesDemoDeletedEvent. It retrieves data using theQueryGateway, sends a notification using theCommandGateway, and marks theSagaStateas ended.
Note: This example doesn't showcase the optimal usage of sagas for handling eventual consistency concerns.
Key Takeaways
@SagaEventHandlerempowers sagas to react to relevant events and manage their state accordingly.Understanding
@SagaEventHandleris essential for building robust sagas that coordinate long-running workflows within Evento.Effective use of
SagaStateand optional parameters allows for informed decision-making and potential interactions with the domain within the saga logic.
The next chapter might explore a different saga usage example that highlights eventual consistency concerns and how sagas can handle them.
Last updated