2

In my Java application, I try to implement hexagonal architecture:

  • Its inner core package contains the pure business logic. These are in the form of plain old Java objects and interfaces.

  • On one side of the core, I have a port package api that receives requests from the outside and calls the business logic's interfaces to perform the required steps, such as retrieving or creating an entity.

  • On another side, I have an adapter package persistency that implements data retrieval and storage in a database. It is decoupled in hexagonal way, meaning the core provides and calls interfaces, which are implemented by classes in this package.

In a typical scenario, the api receives a REST call, calls the core, and the core calls the persistency-backed adapters for creating/reading/updating/deleting something.

Which of these components optimally creates the database transaction? More precisely, since I use Spring Boot, which package should contain the @Transactional-annotated methods?

  • At first thought, my guess would have been that the core should be in control of this, as consistent rollback seems to be a key task to ensuring consistent business data. However, at second glance, transactionality seems to be more of a database detail, and a different implementation of the adapter, such as sending the data on via REST for outside storage, uses completely different schemes.

  • In non-hexagonal layered architectures, we usually find @Transactional on the uppermost layer, meaning the controllers' methods in the api package. This seems to be in sync with the idea that it is the REST endpoint that decides whether the request is stored completely or partially. However, it seems weird that this border package (api) should manage the details of another border package (persistency). If the core is wired to a different kind of persistency, these Transactionals would become useless.

  • Another idea could be to put the @Transactionals into the persistency package. But this would mean that other packages are completely unable to control the level of detail and multiple database accesses might result unintentionally in a series of transactions instead of one common one.

Florian
  • 4,821
  • 2
  • 19
  • 44

1 Answers1

0

I agree with you that making any of core, api or persistence responsible for transactions does not look well.

Usually, there is one more layer, called like Application, which is placed between controllers (api layer in your case) and domain logic (your core). This Application layer contains application use-cases, and is responsible of high-level coordination: managing transactions, dispatching integration events, etc.

But adding a whole new layer only for one task may be an overhead for your case. If you are sure that there will not appear any other tasks fitting "somewhere in between" api and core, I'd suggest to put transaction management in the api layer.

But I'm pretty sure that this is not the only concern in your app, which fits "in between". So, adding Application layer may be worth it. For example, writing logs, or triggering integration events are also good candidates to be placed here.

P.S. Also your words "If the core is wired to a different kind of persistency, these Transactionals would become useless." make me suspect that coupling between your core and persistence is too high. Ideally, core should not be "wired" on any persistence mechanisms, and abstracting via interface is still dependency, though inverted. What I'd suggest is adding app layer which will be "wired" on persistence layer, instead. Then core will be isolated completely. I offer you to read this amazing article about core isolation and completion, it will clarify why the additional layer may be required: https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness. And this one will explain why inverted dependency on persistence is still dependency, and thus should be avoided: https://enterprisecraftsmanship.com/posts/how-to-know-if-your-domain-model-is-properly-isolated

Ilia Yatsenko
  • 779
  • 4
  • 7