DDD应用层和持久化事务

Joh*_*all 1 architecture design-patterns domain-driven-design onion-architecture

DDD 的领导者将应用层视为事务管理的适当位置。例如,来自文斯·沃恩:

应用程序服务驻留在应用程序层。[...]。他们可以控制持久性事务[...]”。

这是否不会将基础设施问题泄漏到应用程序层中,因为持久性(更具体地说,其实现细节)不是应用程序层应该意识到的事情?

虽然我可以在应用程序层中定义一个契约来满足基础设施层的具体要求,但这仍然感觉有点像泄漏实现细节。虽然这确实实现了与实际持久性具体的解耦,但我认为它并不会使应用程序层真正忽略持久性实现细节。这难道不是我应该关心的事情吗?

下面是一个伪 PHP 的示例,展示了它的外观:

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;
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

作为开发人员,我们知道事务很重要,即使遵循“DDD 规则”并且每个Order事务仅修改一个聚合根 ( ) 也是如此。我们知道Order有许多OrderLines我们理解的内容将被写入(例如)多个数据库表。

但是为什么应用程序会意识到事务的持久性特定的细微差别呢?基本上,我觉得这TransactionManager是一个错误的抽象,因为它间接绑定到某种持久性机制。我们甚至在抽象中使用来自实现细节的语言,这对我来说又是错误的。

最后,我确实认识到最佳实践和建议可能并不总是每次都能完美地发挥作用,作为开发人员,我们可以选择“打破规则”。但是我觉得因为这种情况很常见,所以我一定误解了应用程序层可以“意识到”什么。

谢谢你!

Voi*_*son 5

这难道不是我应该关心的事情吗?

很大程度上,是的。

解释#1:领域驱动设计的动机是获得正确的领域模型,因为这就是金钱/杠杆/竞争优势所在。作为一家银行、一家货运公司、一家冰淇淋店,您不会根据您的基础设施代码来主宰您的市场。您将依赖于拥有出色的流程自动化,特别是拥有可让您适应不断变化的市场条件的流程自动化。

如果您对事务管理器大惊小怪,那么就表明出现了非常非常错误的情况。

需要考虑的一种可能性是您的代码是否想要耦合到“抽象”TransactionManager,以 PDOTransactionManager 作为可能的实现之一,或者您的代码是否应该耦合到抽象 PDOTransactionManager,以 LivePDOTransactionManager 作为可能的实现。(“重复比错误的抽象更便宜”——Sandi Metz)

解释#2:没有人因为遵守 DDD“规则”而提供补助金/奖金/奖品。约束的价值在于它们所引发的属性。

除非您拥有相反的重要证据,否则您应该对以下可能性保持开放态度:

  • 承诺的财产并不像声称的那么有价值
  • 约束实际上并没有产生所承诺的属性

但是为什么应用程序会意识到事务的持久性特定的细微差别呢?

在讨论 REPOSITORY 模式时,Evans (2003) 写道:

将交易控制权留给客户。尽管存储库将在数据库中插入和删除,但它通常不会提交任何内容......客户端可能具有正确启动和提交工作单元的上下文。如果 REPOSITORY 放手,事务管理将会更加简单。

应用程序代码在这里起作用是因为(a)它是与存储库交互的应用程序代码,并且(b)它是理解该工作单元的上下文的应用程序代码。

我目前的解释:Evans 描述的持久性模式与 2003 年左右 Java 解决方案的一些常见假设紧密耦合 - 例如,所有信息都在(1)数据库中。当细节始终相同时,很容易“隐藏”细节。

但是再添加一个数据库,突然一堆代码需要意识到,至少隐式地,我们无法原子地锁定和更新两个数据库(当您想要对数据库和数据库进行原子更新时,您会遇到类似的问题) “公共汽车”)。

代码需要了解代码需要了解的上下文。


另一个论点;在事务提交之前或之后调度域事件?

你真的想在一个满是昂贵律师的房间里,试图解释你的系统有时会做出承诺而不把它们写下来吗?