Hexagonal or ports & adapters architecture is commonly used to structure the services so that details of the communication (including APIs and database access) do not leak into the business logic code.
One problem with hexagonal architecture is that you need to map your data a lot between objects used in your API and persistence (ports) and your business logic (domain). Of course, tools like MapStruct for Java help in this.
With the mappers in place, a pattern commonly emerges where at the beginning of an operation a persisted entity is read from a database, it is mapped to a domain object, some updates are made in the domain module and the updated domain object, after being mapped back to the persistence entity, is saved to the database.
This pattern has a problem that it does not handle concurrent updates out of the box. If the database record is changed by someone else between your database read and write, your write blindly overwrites the record with old data, leading to data loss and/or corruption.
Transactions might help, but you would need to remember to start the transaction and ask for an exclusive lock (SELECT FOR UPDATE in SQL) at the time you read the data. And you have to make sure the span of your transaction is until you save the modified data back.
But transactions and exclusive locks create an overhead that can slow down your system.
One option is to refactor your persistence code so that it provides methods that do atomic updates of exactly that part of your document that needs to be updated by the business operation. For that, you might need to stop using the repository pattern and go straight to e.g. MongoTemplate to have access to those atomic operations. And of course it has a big impact on the overall design of your application (service), you cannot do "read full object-map-modify-map-save full object" anymore.
On the other hand, if you do not expect many concurrent updates, you can avoid that refactoring by introducing optimistic locking. Optimistic locking is no locking actually, it means adding a version number field to your persisted data and when writing back, checking if the version matches.
Now the question is, how to preserve the version number when your database entity is mapped to the domain object and back? From my analysis and research it usually means that you need to relax the hexagonal architecture rules a little bit. For example, you can decide to put the version field in the business object even though it feels like it is specific to the persistence adapter.
Here are some interesting links
Hexagonal Architecture and database concurrency (StackExchange)
Building an Event Driven Architecture – Lessons Learned (see the comments below the article discussing the optimistic locking)
Top comments (0)