Microservices and the Saga Pattern, Part 3
In the final part of the this three-part series, we finally dive into the saga pattern and how it can be used microservice-based applications.
Join the DZone community and get the full member experience.Join For Free
Now, moving on to the third point of our discussion, let's dive into Saga Patterns.
A reactive system is message-driven, and it puts emphasis on asynchronous and non-blocking messages. Ideally, we should always go with asynchronous messaging between components but we can have synchronous messages as well. It is important to understand here that the need for synchronous messages should be driven by the domain requirements rather than technical convenience. Sometimes being asynchronous in nature doesn’t help a lot. Consider a scenario:
In a distributed system, we want to do a database transaction. Our application is spread across multiple microservices, and they are all communicating asynchronously. Trying to open a transaction when you’re potentially accessing multiple databases doesn’t actually really work. And, even if you could do that, because of the nature of the asynchronous messages, you’d have to hold that transaction open for a potentially long period of time, which makes things very brittle. Sometimes things like this happen where we have multiple stages, multiple steps, all of which have to complete, or all of which have to fail. That’s how a transaction works, but when this spreads out across multiple microservices we can’t simply use a transaction. What should be used then? The answer to that is the saga pattern.
The saga pattern is a way of representing a long-running transaction. What we do is have multiple requests, managed by a saga. These requests can be run in sequence or in parallel. When all the requests are complete and have all completed successfully, then we can say that our saga is completed. But what happens if one request (R1) finished successfully but another request (R2) failed? How do you deal with this failure? The answer to that lies within the implementation of the saga pattern. In the saga pattern, each request is paired up with a compensating action. If a request fails, compensating actions are executed for all the completed steps in that request. Once such a scenario occurs we do not complete the saga, instead, we fail the saga, and if the compensating action also failed then we retry the compensating action until it succeeds.
We should not confuse a compensating action with a rollback. A rollback implies that a transaction has not completed, but when we rollback we erase the evidence of the transaction and that way we don’t even know that a transaction was initiated and it failed. We say that the thing we were trying to do never happened, but this is not the case with a compensating action in sagas. With compensating actions (or Sagas) we acknowledge that the thing we were trying to do did not complete successfully and, as a consequence, we are applying fixes in the form of our compensating actions to whatever change was made to the state of the system by this request (R2) prior to its failure. The evidence of original actions would still remain and we are being honest and transparent to the customer about the scenario/transaction/thing that happened/failed.
A real-world example of the saga pattern in use can be found in the banking sector. Let's suppose you tried to initiate a transaction, “debit 500 bucks,” from your bank account while making a purchase from a nearby stationery store, but due to some certain factors you received a notification that your account was debited but the shopkeeper didn’t receive the payment, so they asked you to do a transaction (“debit 500 bucks”) again and this time it succeeded. Now when you would see your bank statement you would see two transactions on the same date for the same entity you purchased and now you are curious/stressed about: did that shopkeeper lie to you? Did he charge you twice for the same item? etc. But that is not true nor was the shopkeeper lying nor were you charged twice for the same item! But how come two transactions are there for “debit 500 bucks” yet only your account was only debited once?
The answer is saga patterns were enabled for your transaction, and this condition (failure in transaction-1) is where they came into the picture. So your first transaction request that failed initiated a compensating action on its failure that provided fixes to all the operations that were performed before your request failed, so what happened is that 500 bucks was debited from your account but since the request failed immediately, a compensating action paired with the request (debit 500 bucks) was executed that resulted in a command to the system, and that command was executed on the state of your bank account. This way your money never left your bank account and it didn't reach the shopkeeper the first time. The next time you initiated the same transaction and it completed without any failures, your money transferred to the shopkeeper’s account. So, if you would look carefully at your bank statement details, you would see that there aren’t just two transactions that were made to that shop from your account that day, instead, there are three transactions. That would look like:
- (Transaction-1) Debited 500 bucks from your account for payment at the shop.
- (Transaction-2) Credited 500 bucks to your account via a refund, etc.
- (Transaction-3) Debited 500 bucks from your account for payment at the shop.
That is how a saga pattern works and you can clearly see that there must have been some issue with Transaction-1 that resulted in Transaction-2 taking place, and so you were asked to perform Transaction-1 in the form of Transaction-3 again by the shopkeeper.
I hope that explains the basic idea of a saga pattern in action!
In this series, we saw what a microservice is, what it takes for an SOA or service to be categorized as microservice, some pros and cons of working with microservice architecture, went through the fundamentals of the reactive architecture, and looked at the saga pattern in action with a real-world example.
This article was first published on the Knoldus blog.
Published at DZone with permission of Prashant Sharma, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.