'DDD Application Layer and Persistence Transactions

Leaders in DDD cite the Application Layer as the appropriate place for Transaction Management. For example, from Vince Vaughn:

Application Services reside in the Application Layer. [...]. They may control persistence transactions [...]".

Is this not leaking an Infrastructure Concern into the Application Layer, being that persistence (more specifically, its implementation details) are not something the Application Layer should be aware of?

While I can define a contract in the Application Layer to be satisfied by an Infrastructure Layer concretion, this still feels a bit like leaking an implementation detail. While this does accomplish decoupling from the actual persistence concretion, I feel it does not make the application layer truly ignorant of persistence implementation details. Is this simply not something I should be concerned about?

Here is an example in pseudo PHP of how this would look:

namespace Acme\Application\Contracts;

// A contract in the Application Layer to be satisfied by an infrastructure implementation
interface TransactionManager {
    public function begin() : void;
    public function commit() : void;
    public function rollBack() : void;
}

namespace Acme\Intrastructure\PDO;

class PDOTransactionManager implements TransactionManager {
   private PDO $pdo;

   public function begin() : void {
      $this->pdo->beginTransaction();
   }

   // ...
}

namespace Acme\Application\Modules\Ordering;

// A context agnostic application service
final class PlaceOrder {
   private TransactionManager $transactionManager;
   private OrderRepository $repository;
   private OrderFactory $factory;
   private LoggerInterface $logger;
   private EventDispatcher $eventDispatcher;

   public function __invoke(PlaceOrderInput $input)
   {
       // ...

       try {
          $this->transactionManager->begin();

          // we developers "know" writes to multiple database tables, mandating a transaction
          $this->repository->persist($order);

          // another argument; dispatch domain events before or after transaction commit?
          $this->eventDispatcher->dispatch($order->releaseEvents());

          $this->transactionManager->commit();          
          
       }catch(CouldNotSaveException $e) {
          $this->transactionManager->rollback();
          throw $e;
       }
   }
}

As developers, we understand that the transaction is important, even when following "the DDD rules" and modifying only one aggregate root (Order) per transaction. We know that the Order has many OrderLines which we understand will be written to (e.g.) multiple database tables.

But why is the Application aware of the persistence-specific nuance that are Transactions? Basically, it feels to me that the TransactionManager is a false abstraction, as it is indirectly bound to some persistence mechanism. We're even using language from the implementation detail in the abstraction, which again feels wrong to me.

In closing, I do recognize that best practices and recommendations may not always work perfectly every time and as developers we can choose to "break the rules" circumstantially. However I feel that because this scenario is so common, I must be misunderstanding something about what the application layer can be "aware" of.

Thank you!



Solution 1:[1]

Is this simply not something I should be concerned about?

Largely, yes.

Explanation #1: the motivation for domain driven design is getting the domain model right, because that's where the money/the leverage/the competitive advantage lives. You as a bank, a cargo shipping concern, an ice cream shop, are not going to dominate your market on the basis of your infrastructure code. You're going to make bank on having great process automation, and in particular having process automation with affordances that let you adjust to changing market conditions.

If you are fussing over the transaction manager, something has gone Very Very Wrong.

One possibility to consider is whether your code wants to be coupled to an "abstract" TransactionManager, with PDOTransactionManager as one of the possible implementations, or if instead your code should be coupled to an abstract PDOTransactionManager, with LivePDOTransactionManager as a possible implementation. ("Duplication is cheaper than the wrong abstraction" -- Sandi Metz)

Explanation #2: nobody is offering grants/bonuses/prizes for obeying the DDD "rules". The value of constraints is in the properties they induce.

Unless you are in possession of significant evidence to the contrary, you should stay open to the possibilities that

  • the promised properties aren't really as valuable as claimed
  • the constraints don't actually induce the promised properties

But why is the Application aware of the persistence-specific nuance that are Transactions?

In discussing the REPOSITORY pattern, Evans (2003) writes:

Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything.... the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

Application code works here because (a) it's the application code that is interfacing with the repository and (b) it's the application code that understands the context of this unit of work.

My current interpretation: the persistence patterns described by Evans are rather tightly coupled to some common assumptions of java solutions circa 2003 - for instance that all of the information is in the (1) database. It's easy to "hide" the details when the details are always the same.

But add just one more database, and suddenly a bunch of code needs to be aware, at least implicitly, that we can't lock and update both databases atomically (you run into similar issues when you want atomic updates to both the database and the "bus").

The code needs to have awareness of the context that the code needs to be aware of.


another argument; dispatch domain events before or after transaction commit?

Do you really want to be in a room full of expensive lawyers, trying to explain that your system sometimes makes promises without writing them down?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 VoiceOfUnreason