In this post, we explain the importance of maintaining a transaction boundary in the context of Spring + Hibernate or SOA architecture.
The use case
An entity (Employee) updates with business logic that is dependent on another entity (EmployeeMetaData). The Employee entity is fetched using hibernate/ORM. However, we were not able to read uncommitted data leading to other failure scenarios.
We followed the standard architecture of Controller -> Service -> DAO (Data Access Object)
We tried multiple solutions, such as reading uncommitted data and removing transactions. However, as this data is important and sensitive, the risk of eliminating the transaction is high. It is also not good practice to manage database-level operations without transactions.
The Spring framework takes care of Transaction management. The standard architecture is to have each service as transactional. This transactional nature aids in rollbacks in case of exceptions or errors.
In one particular scenario, we assumed a specific value had been updated in the database to update the other dependent entities. Since there existed a colliding transaction, we could not read uncommitted data causing inconsistencies.
Shown below are a pictorial representation and stepwise explanation of the sameThe above representation is explained in the steps listed below:
Step 1: Employee Controller updates the employee metadata using EmployeeService and will update the entity EmployeeMetadata.
Step 2: Employee service has a method called updateEmployeeWithMetaData that the controller invokes. The transaction is initialized post which the EmployeeMetaDataDao method is called. The same transaction also asynchronously calls the GeoLocationService.
Step 3: The EmployeeMetaDataDao method writes and commits the transaction to the database table EmployeeMetaData.
Any exceptions to steps 2 or 3 lead to the transaction rolling back.
Step 4: As mentioned in step 2, a method updateLotWithLocationData from GeolocationService invokes asynchronously and updates the Employee entity. This assumes that EmployeeMetadata is being updated in the course of time.
Shown below are a pictorial representation and stepwise explanation of the same.
Step 5: Now, EmployeeDao will try and fetch the updated values from EmployeeMetaData and will update the Employee entity based on those values. However, the EmployeeMetaData is not committed to the database yet.
Side effects
The dotted squares in the pictorial below show the transaction boundaries for different transactions.
In this case, until the first transaction commits the data to the database, it tries to read the latest data which is not committed to the database. This is because the transactions collide and the asynchronous nature of calling the other service.
The solution
Shown below are a pictorial representation and stepwise explanation of the solution and the results.
Step 1: Employee Controller updates the employee metadata using EmployeeService and will update the entity EmployeeMetadata.
Step 2: Employee service has a method called updateEmployeeWithMetaData that invokes from the controller, and the transaction is initialized, after which the EmployeeMetaDataDao method is called in the same transaction. Earlier, we were calling GeoLocationService in this step, which is now removed to allow completion of one full transaction.
Step 3: The EmployeeMetaDataDao method will write and commit the transaction to the database table EmployeeMetaData.
Any exceptions in steps #2 or #3 will result in the transaction being rolled back.
Step 4: Now, we return the control of execution to the EmployeeController, and the transaction completes ensuring that the database is updated with the necessary values. Earlier, we were not returning the flow of execution to the controller; hence everything was taking place in the same transaction or boundary.
Step 5: A new call is made to GeoLocationService, which will initiate a new transaction and call the Dao method and update the Employee entity.
Step 6: Now, the Dao method will continue in the same transaction and will fetch the updated values from the EmployeeMetaData. This will, in turn, update the Employee entity as EmployeeMetaData now has written and committed data to the database as the earlier transaction has been completed in step #4.
Here is what must be kept in mind while devising a solution to such scenarios,
- Identify the transaction boundary for processing the required data
- Process the required data asynchronously; however, this will be outside the original transaction boundary. This allows all that data to be committed, and only the updated data will be read.