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 Service
will create a payment transaction and produce anInvoice Update Event
. - The
Invoice Service
listens to this event to update the given invoice status topaid
. - This will also produce
Debtor Update Event
signaling theDebtor Service
for the next operation. - The
Debtor Service
listens to this event and do the outstanding calculation update. - It will then produce an
Outstanding Deducted Event
that will communicate back to thePayment Service
. - The
Payment Service
receives an updated that a successful debtor outstanding balance was reflected ending the payment transaction flow.
Compensating (Rollback) Transaction
- The
Payment Service
will create a payment transaction and produce anInvoice Update Event
. - The
Invoice Service
listens to this event to update the given invoice status topaid
. - This will also produce
Debtor Update Event
signaling theDebtor Service
for the next operation. - The
Debtor Service
listens 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 Service
will trigger a new event calledInvoice Revert Event
to instruct theInvoice Service
of the next operation. - The
Invoice Service
receives the event and will revert the invoice status back tocreated
. - It will then produce a
Payment Revert Event
to signal thePayment Service
. - The
Payment Service
receives 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 Service
creates a payment transaction and triggers to start thePayment Service Orchestrator
.Payment Service Orchestrator
produces anInvoice Update Event
that will tellInvoice Service
what to do.- The
Invoice Service
receives the event and updates the invoice status topaid
. - This will produce a succesful invoice update response back to
Payment Orchestration Event Reply
. Payment Service Orchestrator
receives the event of a successful invoice update operation.- After a successful invoice status update, it will trigger a
Debtor Update Event
. - The
Debtor Service
receives 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 Orchestrator
of a successful debtor update operation. - Then
Payment Service Orchestrator
will tell thePayment Service
that a payment process was successful.
Compensating (Rollback) Transaction
Payment Service
creates a payment transaction and triggers to start thePayment Service Orchestrator
.Payment Service Orchestrator
produces anInvoice Update Event
that will tellInvoice Service
what to do.- The
Invoice Service
receives the event and updates the invoice status topaid
. - This will produce a succesful invoice update response back to
Payment Orchestration Event Reply
. Payment Service Orchestrator
receives 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 Service
to update a debtor outstanding balance. However, this operation failed to update debtor outstanding balance for some reason e.g. a validation error. - The
Debtor Service
will produce a response toPayment Orchestration Event Reply
that a debtor update failed. - The
Payment Orchestration Event Reply
will signalPayment Service Orchestrator
of the failed debtor update operation. - Then
Payment Service Orchestrator
will trigger aInvoice Revert Event
. - To instruct
Invoice Service
to revert invoice status back to the initial state e.g.created
. - Then the
Invoice Service
will return a response to thePayment Orchestration Event Reply
of a successful invoice status revert. - This signals the
Payment Service Orchestrator
of a successful invoice state rollback. - And will tell
Payment Service
that 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.