in Coding

Saga pattern with Spring Boot and ActiveMQ

During the last days, I dug a little bit into the Saga pattern. The Saga pattern is an architectural pattern which provides an alternative approach to big and long running ACID transactions. It takes a business process and breaks it up into small isolated steps – each of them with its own transaction. The overall consistency is provided by manually reverting past actions.

Example

Imagine a system to submit reviews (very much like I described in my boilerplate project). An user can submit a review which is checked (by a person or an algorithm) and either approved or rejected. We can implement this behaviour with a couple of events:

  • REVIEW_SUBMITTED_EVENT – The first event. It starts our business process as well as the saga.
  • REVIEW_IN_EXAMINATION_EVENT – A second arbitrary event. It represents an action such as informing the customer via e-mail. In this example, it’s just a placeholder to make everything a little bit more complicated.
  • REVIEW_APPROVED_EVENT – The last event. In this case, the checking was successful and the review is accepted. There’s nothing more to do. This event ends our saga.
  • REVIEW_REJECTED_EVENT – A compensating event. It also ends our business process, but in the way of a rollback. For example, it deletes the rejected review.

The saga

How could a saga look like? A saga basically encapsulates the individual steps of a business process. It keeps track of what has happened and what is still missing.

Note that there are many ways a saga could look like. None of them is right or wrong. The following just show a simple approach to solve the example above.

As soon as we get a REVIEW_SUBMITTED_EVENT we create a new saga to represents a new checking-process:

After saving the saga, we “apply” the first event to it.

Event handling

For handling events, ActiveMQ is used. There’s a JMS listener for each event and – except of the initial setup of the saga – every event listener looks the same.

By calling apply(...) we give the control flow to the saga. Instead of the listener itself or a service, the saga will decide what to do. It knows the current status of the overall process as it stores all previous events.

A lot of things can happen now:

  • Check if a saga exists
  • Check if the event is required and belongs to the saga
  • Check if the event has already occurred (duplicate)
  • Check if the event came in the right order
  • Invoke a handler method for the event on the handler associated with the sage (reviewCheckingService)
  • Check if the sage is completed by the event
  • Check if the sage is cancelled by the event

Note that those steps are only suggestions. You can do one of them or all of them. It totally belongs to your use-case. Maybe you process doesn’t require a certain order of the events or don’t mind about duplicate events.

Doing things

We already defined how a saga could look like and how we listen for events to apply them to the saga. The saga decides what to do with the event and might call a handler method on the service.

Note: The code snippet above is just an example to illustrate the invocation of the handler. It leaves out a lot of things: check if the event is required, check for duplicate events, etc. It also ignores if no handler method exists and relies on a naming convention for the handler (the handler method must be called on(...)).

When applying an event, we can also check if it completes the saga. If so, we can execute some special handler method:

Rollback

So far, we only considered the happy path. Every event comes in the right order, no duplicates and no exceptions. But what happens in case of an error?

In a traditional ACID transaction, we would do a database rollback (instead of a commit). However, this breaks as soon as we do things outside of the database like REST calls, sending an e-mail or writing to a file. We cannot rollback those things. Even worse: If we rollback the database, we might have inconsistencies as the other tasks are not rolled back. The REST is made, the e-mail is sent and the file is written.

Instead of a technical rollback (which is very limited) the Saga pattern uses compensating events to react to exceptions. If something goes wrong, a compensating event is thrown which “cancels” the Saga and reverts all things done.

In the example above, the compensating event is the REVIEW_REJECTED_EVENT. It tells use that we must revert things. Note that we cannot relay on a technical solution. Instead we must write business code to explicitly handle the error situation. E.g. we must delete the review from the database, remove a line from a CSV file or we must send an e-mail with an apology.

Best regards,
Thomas

More

Write a Comment

Comment