DEV Community

marian-varga
marian-varga

Posted on • Originally published at dastalvi.com

Hexagonal+optimistic may not be easy

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)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

👋 Kindness is contagious

If this article connected with you, consider tapping ❤️ or leaving a brief comment to share your thoughts!

Okay