In a monolithic system, a database transaction should ensure consistent query results after a successful request. Otherwise, it should rollback all the commits that were made to the previous state (Atomicity). Atomic transaction is common and easily achievable in a single database system.
However, in a distributed system you may need to handle this differently ensuring data consistency across multiple services and where each service have its own database. So traditional ACID approach wouldn't work.
Problem
For example, we have three services: Payment service, Invoicing service and Debtor service.
Imagine a debtor initiate a full payment to an invoice and we want to capture that transaction through the payment service then set the invoice status into "paid" and update the debtor outstanding balance.
Solution
One of the most common approach to achieve transaction that spans multiple services is by using the Saga Pattern.
A Saga is a sequence of local transactions and each transaction will publish messages or events that trigger the next local transaction.
If something goes wrong to a participating microservice, there should be a compensating transaction to undo the changes that were made by the preceding local transactions.
Now there are two popular Saga based approaches.
- Choreography-based Saga
- Orchestration-based Saga
Choreography-based Saga Example
It is when microservices publish a message/event from a local transaction and trigger subscribers or participating microservices for the next local transaction. See image below as an example that solves the given Problem:
- The
Payment Servicewill create a payment transaction and produce anInvoice Update Event. - The
Invoice Servicelistens to this event to update the given invoice status topaid. - This will also produce
Debtor Update Eventsignaling theDebtor Servicefor the next operation. - The
Debtor Servicelistens to this event and do the outstanding calculation update. - It will then produce an
Outstanding Deducted Eventthat will communicate back to thePayment Service. - The
Payment Servicereceives an updated that a successful debtor outstanding balance was reflected ending the payment transaction flow.
Compensating (Rollback) Transaction
- The
Payment Servicewill create a payment transaction and produce anInvoice Update Event. - The
Invoice Servicelistens to this event to update the given invoice status topaid. - This will also produce
Debtor Update Eventsignaling theDebtor Servicefor the next operation. - The
Debtor Servicelistens to this event and do the outstanding calculation update. However, this operation failed to update debtor outstanding balance for some reason e.g. a validation error. - The
Debtor Servicewill trigger a new event calledInvoice Revert Eventto instruct theInvoice Serviceof the next operation. - The
Invoice Servicereceives the event and will revert the invoice status back tocreated. - It will then produce a
Payment Revert Eventto signal thePayment Service. - The
Payment Servicereceives the event and ending the transaction with a failed payment process.
Benefits and drawbacks
- No additional complexity for coordination logic.
- Very simple and easy to implement with smaller workflows.
- Hard to maintain when it grows.
- Cyclic dependency between participating services.
- Hard to tests as you will need to run all applications that are involved.
Orchestration-based Saga Example
It is when microservices have an orchestrator to command what participating microservices should trigger the next local transaction. The participating microservices should send a reply back to the local orchestrator before triggering the next service.
Below is the illustration of the workflow solution of the given Problem above.
Payment Servicecreates a payment transaction and triggers to start thePayment Service Orchestrator.Payment Service Orchestratorproduces anInvoice Update Eventthat will tellInvoice Servicewhat to do.- The
Invoice Servicereceives the event and updates the invoice status topaid. - This will produce a succesful invoice update response back to
Payment Orchestration Event Reply. Payment Service Orchestratorreceives the event of a successful invoice update operation.- After a successful invoice status update, it will trigger a
Debtor Update Event. - The
Debtor Servicereceives the event to update a debtor outstanding balance. - And will produce a successful response of debtor outstanding update to the
Payment Orchestration Event Reply. - This will signal
Payment Service Orchestratorof a successful debtor update operation. - Then
Payment Service Orchestratorwill tell thePayment Servicethat a payment process was successful.
Compensating (Rollback) Transaction
Payment Servicecreates a payment transaction and triggers to start thePayment Service Orchestrator.Payment Service Orchestratorproduces anInvoice Update Eventthat will tellInvoice Servicewhat to do.- The
Invoice Servicereceives the event and updates the invoice status topaid. - This will produce a succesful invoice update response back to
Payment Orchestration Event Reply. Payment Service Orchestratorreceives the event of a successful invoice update operation.- After a successful invoice status update, it will trigger a
Debtor Update Event. - That will call
Debtor Serviceto update a debtor outstanding balance. However, this operation failed to update debtor outstanding balance for some reason e.g. a validation error. - The
Debtor Servicewill produce a response toPayment Orchestration Event Replythat a debtor update failed. - The
Payment Orchestration Event Replywill signalPayment Service Orchestratorof the failed debtor update operation. - Then
Payment Service Orchestratorwill trigger aInvoice Revert Event. - To instruct
Invoice Serviceto revert invoice status back to the initial state e.g.created. - Then the
Invoice Servicewill return a response to thePayment Orchestration Event Replyof a successful invoice status revert. - This signals the
Payment Service Orchestratorof a successful invoice state rollback. - And will tell
Payment Servicethat a payment process did not push through and every state of data is still the same like nothing happened.
Benefits and drawbacks
- Complex saga orchestration implementation.
- No cyclic dependency.
- Distribution of transactions are centralized.
- Testable Orchestrator commands.
- Handle rollback transaction better.