Question

I have been reading Alistair Cockburn's article on Hexagonal Architecture. One question that occurs to me, is how to handle situations where there are database concurrency issues.

An obvious example is a situation where an entity is retrieved from the repository, has an integer attribute incremented, and then is saved back to the repository.

In a web context, its possible that two requests could attempt to increment at the same time, with familiar incorrect results.

An obvious solution would be to do the incrementing in an SQL query (or ORM equivalent). A django ORM example:

Comments.objects.filter(pk=comment.pk).update(vote=F('vote') + 1)

However, hexagonal architecture seems to rule this out, since it would place business logic in the repository.

A similar problem could take place when different requests attempt to modify different fields. For example:

  1. In request A, a comment is retrieved from the repo to edit its content.
  2. In request B, the same comment is retrieved to increment its vote tally.
  3. In request B, the incremented comment is saved to the repo.
  4. In request A, the edited comment is saved to the repo (including the old vote tally value), overwriting the vote tally increment made by in request A.

The following django ORM example shows one way that this can be solved:

comment.save(update_fields=["content"])

But its not obvious how to do this in a "hexagonal" way, where the business logic is meant to take place in "pure" code, unadulterated by database concerns.

Is there a common solution to this sort of problem?

Was it helpful?

Solution

The core idea of the hexagonal architecture is that the business logic defines various interfaces, and external systems are adapted to this interface.

For example, if the business logic wants to be able to update just the body of a comment, then add such a methods to the persistence interface. The database code that implements this interface could then use appropriate SQL (i.e. update only the comment body field, not the comment vote field).

If the business logic wants to perform a number of changes transactionally, write this database adapter in a way that supports transactions.

The hexagonal architecture doesn't rule out using SQL or various ORM features. However, this shouldn't happen within the core business logic, but should be placed in the database adapter. It is not possible to delegate all concurrency concerns to the database adapter, but it might be a core application concern. However, the business logic can define convenient, high-level abstractions that are implemented in an adapter.

Note that it can sometimes be sensible to not go fully hexagonal. In a CRUD app, using a database layer (not adapter!) or even raw SQL can be more convenient, and creating any adapters would just unnecessarily complicate things.

Licensed under: CC-BY-SA with attribution
scroll top