The Saga pattern

The Saga pattern gives a transaction management solution in a distributed environment, where we have different subsystems (microservices) communicating with each other. There are many long-lived transactions in a distributed environment in the real world. Any transaction makes sense in a database if it works completely or fails completely. In the first scenario, our system comes into the consistent state automatically. The main challenge is to bring our system into the consistent state after a transaction fails at any subcomponent.

In Saga, every workflow of a transaction is grouped in a composite task that we call routing slips, which are handed along the activity chain. When an activity completes, it adds a record of the completion to the routing slip, along with information on where its compensating operation can be reached (for example, via a queue). Alternatively, it may register a callback to a compensating transaction in a message routing slip. Finally, it passes the updated message down the activity chain. When an activity fails, it cleans up locally and then sends the routing slip backwards to the last completed activity's compensation address to unwind the transaction outcome. The previous step does the same thing, calling its predecessor compensating transaction, and so on until all the already executed transactions are compensated. Due to their highly fault-tolerant, distributed nature, Sagas are very well-suited to replace traditional transactions when transactions across microservice boundaries are required in a microservice architecture.

The very first component in a Saga transaction pattern is called an initiator, which creates the context of transaction and the reason for the interaction. It then asks one or more other services (participators) to perform some businesses activities. The participators can register for coordination and exchange messages and requests until they reach some agreement, or they are ready to complete the interaction. This is when the coordinator requests all the participants to finalize the agreement (prepare) and commit. If there are any problems during the completion of any activities, you have to perform a counteraction, called compensation, just as you have rollback in regular ACID transactions. In coding layman's language, I should like to rephrase it as: every step or every microservice performs its task, marks its task as done, registers its callback method in the case of failure in any upcoming activity (microservice), and passes its route slip to the next microservice. If anything fails, then the routing slip is passed back to the last completed task by calling its compensation method.

Let's understand it using an example. A user wants to book a table in a restaurant with a specific menu for dinner, and they also need to book a cab to reach the restaurant on time. Here are the steps the user will follow:

  • Check whether a table is available in the restaurant. If not, the transaction will not propagate from here.
  • Look for the desired menu item. If it is not there, there is no point reserving a table. So, the last activity has to roll back.
  • If the table and menu are booked, then book a cab for a particular time. If no cab is available during that time, the user has to cancel the reservation also. So, the last two activities can't be committed; they have to roll back.
The assumption here is that the user is using the same website to make all these reservations in one go.

The Saga pattern is an architectural pattern that focuses on integrity, reliability, and quality, and it pertains to the communication patterns between services. When it comes to designing the implementation of the pattern, everyone has a different version. You can implement the concerns and roles defined in the pattern in a method of your choice. There could be one component, maybe the initiator handling the controls of the flow, like an orchestration pattern, or it could be a distributed pattern.

It is important that each service's persistent data is private, but if we go by database per service strategies, it might be difficult to implement some queries if they have database joins across the data owned by multiple services. Joining data inside a service is possible. In other situations, you will need to use Command Query Responsibility Segregation (CQRS) and maintain denormalized views.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset