Most of the monolithic applications which are using a relational database may use ACID transactions, which provide some important attributes:
- Atomicity – Changes are made atomically
- Consistency – The state of the database is always consistent
- Isolation – Even though transactions are executed concurrently it appears they are executed serially
- Durable – Once a transaction is committed it cannot be undone
Using the ACID properties in the application provides capability of beginning a transaction to perform changes like insert, update, & delete and allows to commit or rollback the transaction.
Unfortunately, applying the ACID properties for data access becomes much more complex when we move to microservices architecture.
This is because microservices architecture follows the Single Responsibility Principle (SRP), it means that each microservice has to maintain and own the data and no other service can access the data without the knowledge of the responsible microservice.
There are a few different ways to keep a service’s persistent data private. It is not necessary to provision a database server for each service, if you are using a relational database then the options are:
- Private-tables-per-service – each service owns a set of tables that must only be accessed by that service, a logical separation.
- Schema-per-service – each service has a database schema that’s private to that service, schema based separation
- Database-server-per-service – each service has its own database server, Server based separation.
Private-tables-per-service and schema-per-service have the lowest overhead. Using a schema per service is appealing since it makes ownership clearer. Some high throughput services might need their own database server.
By implementing the SRP, microservice is loosely coupled and always evolves independently of one another.
Distributed Transaction – Event Driven Architecture & Two Phase Commit
This generates the most consistent system but impacts negatively in the performance of the system due to the two-phase commit that synchronizes and updates multiple microservices through service calls by locking the shared data.
This means that each step of transaction publishes an event which triggers the next stage; each event has an unique ID and must be acknowledged that it has been processed, and only then is transactional piece within the microservice marked as committed.
Let’s say we have two microservices and respective DB which stores Fund Transfer data from one bank account to another, and one Service initiates the transfer by debiting the amount from the account holder and another service which handles updating the transferred data to different bank account after validating the details, we can have the following table:
id | date | acc_no | amt_debit | completed
1234 | 2016-06-14 | 78910 | 20000 | false
id | date | acc_no | from_act | amt_credit |completed
1020 |2016-06-14 | 2345 | 78910 | 20000 | true
In this case, the column completed indicates whether transaction should be committed, ie. Microservice 1 debits the amount and marks as not completed, since the amount is not yet credited in the targeted bank account. Then Microservice 1 invokes Microservice 2 to complete the transaction. When Microservice 2 completes its work by crediting the amount in the targeted bank account, it notifies Microservice 1 (either synchronously or asynchronously (Event State Triggering)). After receiving notification, Microservice 1 marks the fund transfer as completed.
Incase Microservice 2 fails to complete its work by crediting the amount in the targeted bank account, it notifies Microservice 1 (either synchronously or asynchronously (Event State Triggering)). After receiving notification, Microservice 1 marks the fund transfer as not completed, by revoking (deleting the record) the transaction.
In microservices architecture, each microservice has its own private datastore. Different microservices might use different SQL and NoSQL databases. While this database architecture has significant benefits, it creates some distributed data management challenges. The challenge of decentralized data management is how to implement business transactions that maintain eventual consistency
In the industry standard, most of the application solutions use an event-driven architecture to atomically update the state and publish events using the database as a message queue and event sourcing.
Read our recent paper on Microservices.