Data consistency across microservices in dispersed transaction scenarios can be managed with the help of the Saga design pattern. A saga is a series of transactions that updates each service and broadcasts a message or event to start the subsequent transaction step. The saga executes compensating transactions that cancel out the previous transactions if a step fails.
Before we talk about the Saga microservice pattern, let us talk about what necessitates it. Along with the rise of the microservice pattern, decentralization became a key theme within all aspects of software development, especially with databases. Centralized databases were convenient and easy to maintain, but scaling them with demand was a complete nightmare and it was impossible to assign ownership of the data, hence a database-per-service model came into view.
It is much easier to have each microservice handle its database, as it already conforms to microservices’ key idea of complete isolation of services, and allows for loose coupling and much greater flexibility. However, this comes with its own set of challenges.
One such functional challenge is a collaboration between services when the data, itself, is isolated. One can expose endpoints to query whatever data they might need from a particular microservice, to maintain ACID properties it is essential to have a ledger of all the changes made in all the individual databases. This ledger of activity is known as a SAGA in microservices.
All the ACID rules still apply in a Saga, albeit in a distributed manner, so compensating transactions have to make sure that all the individual databases are updated accordingly.
Now, coming back to SAGA, a transaction in SAGA is a discrete chunk of logic or labour that occasionally consists of several operations. An event occurs when the status of an entity changes during a transaction, and a command contains all the information required to carry out an action or start a subsequent event. Atomic, consistent, isolated, and long-lasting transactions are required (ACID). Although transactions within a single service are ACID, a cross-service transaction management technique is necessary to ensure data consistency.
According to multiservice architectures:
- Atomicity is a set of actions that cannot be divided or reduced; all must take place or none at all.
- Consistency refers to the fact that the transaction only transfers data across legitimate states.
- Isolation ensures that concurrent transactions result in the same data state as transactions that were done sequentially.
- In the event of a system failure or power loss, durability makes guarantee that committed transactions are kept committed.
Choreography Sagas rely on events and their respective handlers in each microservice. For example, for any online delivery service, there have to be two essential services. An ordering service and payment service. A typical order sequence would then look as follows.
1. The customer places the items in her cart and places the order, this generates an event toward payment service.
2. This leads the customer toward the payment service, where she provides details of the payment and goes to the payment gateway.
a. The customer authenticates on the payment gateway, if she’s successful in authenticating, and the payment goes through, another event towards order service will be generated indicating payment completion and successful order.
b. Instead, if the payment fails, a failure event is generated. This event is received by the order service and is marked failed.
These sagas have a separate microservice that takes care of orchestrating the events instead of services handling them themselves. The flow of events is nearly the same, with the only difference being the service talking to the orchestrator instead of each other. The example above would look as follows in the case of orchestration.
1. The customer places the items in her cart and places the order, this generates an event towards the orchestrator.
2. Orchestrator receives the above event and instructs the Payment service to handle the payment.
3. The payment service relays the information about the payment status to the orchestrator through another event.
4. Upon receiving this event, Orchestrator then accordingly generates a success or failure event towards the Order service to mark the order.
These patterns make it a little complex to develop microservices because of the need for compensating transactions but they help your microservices maintain consistent data throughout the stack without the usage of 2pc or distributed transactions.