【问题标题】:AggregateRoot A validation needs informations from AggregateRoot BAggregateRoot A 验证需要来自 AggregateRoot B 的信息
【发布时间】:2021-06-26 12:31:51
【问题描述】:

我是 DDD 领域的新手,我想知道如何处理我有一个聚合根 (AggregateRootA) 需要来自其他聚合根 (AggregareRootB) 的一些信息才能做某事的情况(例如更改 AggregateRootA 的状态)。

如果我尝试使用更真实的场景,这可能会更清楚......让我们考虑一个预订系统。在系统中,我们有 Reservation 聚合根和 Resource 聚合根。资源具有可用/不可用状态。系统允许创建预留并向其添加资源(在代码中,它当然是通过 Id 引用完成的)。下一步是接受预订,在这一步中,预订必须检查其所有资源是否处于可用状态。假设它不能在添加步骤中完成,或者只是该资源在它已经处于保留状态时改变了它的状态。如何进行这样的验证?

我想出了一个解决方案,让 Reservation 可以访问 ResourceRepository,但我发现不应允许 AggregateRoot 在 DDD 中拥有此类访问权限。

我的第二个想法是将验证移至 ApplicationService,但对我而言,此类验证看起来像域逻辑,因此应将其放置在域模型中。

我最后的想法是,也许我应该编写专门的 DomainService 来处理这种情况,但再次...... DomainService 是否应该有权访问存储库?

【问题讨论】:

    标签: domain-driven-design ddd-repositories ddd-service


    【解决方案1】:

    我认为您在最后一段中已经接近了这一点,但问题不是 应该 DomainService 可以访问存储库,更多的是关于可以 它有(在您的技术实施中)?在技​​术基础上评估该方法的优缺点,如果没有出现真正的坏事,那就去做。对我来说,这似乎是你的大脑一直在告诉你要做的事情。

    然后随着时间的推移,你对周围领域、限界上下文、语言等的理解会变得更好(而且会),你总是可以进行重构和重构。没有什么是一成不变的。

    【讨论】:

      【解决方案2】:

      这是一个非常常见且很容易解决的问题,但您需要退后一步。

      您基本上应该有两个有界上下文 - 保留和资源。 保留不能访问任何其他有界上下文的任何类/服务/对象,因为它违反了封装规则。

      您应该改为在 resources 有界上下文中创建查询处理程序。 resources 应该共享查询和响应类,它们将提供您需要的信息 - 公共 API,它是您的域之间的合同。最重要的是,reservations bounded context 不应该知道 resources 的任何实现细节。

      PHP 中的示例:

      资源域:

      请求信息:

      class IsResourceAvaiableQuery
      {
          private int $resourceId;
      
          __construct(int $resourceId)
          {
              $this->resourceId = $resourceId;
          }
      
          public function getResourceId(): int 
          {
              return $this->resourceId;
          }
      }
      

      要获得一个不应该是简单类型,而是由类表示的响应:

      class IsResourceAvaiableDTO
      {
          private bool $isAvailable;
      
         __construct(bool $isAvailable)
         {
             $this->isAvailable = $isAvailable;
         }
      
         public function isAvailable(): bool 
         {
             return $this->isAvailable;
         }
      }
      

      请注意,查询和响应 DTO 都是不可变对象 - 您不能改变它们的状态。

      现在在 reservation 域中:

      class ReservationValidator
      {
          
         public function validate(Reservation $reservation): ViolationsList 
         {
             $isResourceAvailableQuery = new IsResourceAvaiableQuery($reservation->getResourceId());
             $isResourceAvailableDTO = $this->messageBus->query($isResourceAvailableQuery);
      
      
            if (false === $isResourceAvailableDTO->isAvailable()) {
                // mark $reservation as invalid  
            }
         }
      }
      

      总结

      这样您就有了一个单一的事实来源,并且 reservation 域不需要知道您如何确定资源是否可用。此外,您可以随时更改实现,只要您不更改合同(IsResourceAvaiableQuery & IsResourceAvaiableDTO),它就不会影响 reservations 域。

      请注意,资源可能在两个域中以不同的方式可用,您也需要进行拆分:

      • 资源 - 可供预订 - 存在并标记为可操作的项目
      • reservations - 项目尚未预订。而且您需要合并来自两个域的决策才能全面了解情况。

      【讨论】:

      • 使用这种方法,预留有界上下文将依赖于资源有界上下文,我们不应该避免这种与预留有界上下文中的资源数据副本的依赖关系吗?
      • 它不依赖于另一个有界上下文,而是依赖于它的 API。 API 是一个公共合约,虽然上下文实现可能会改变,但 API 不应该。维护 API 层比使用内部存储库或另一个域的其他部分要容易得多。这就像在门口使用门铃一样。我得到了有人会上门的社会契约。您不得直接进入其他人的房屋并与居民(房屋内部人员)交谈。您使用公共 API(门铃)来获取联系响应(某些人类实例),但您不知道哪个实现(例如 Bob、Jane)
      • 另一方面,您不应该将某个上下文所拥有的数据副本发送给另一个上下文。您应该发送对所提问题的答案,并在早上发送更多信息。应转换内部结构以实现联系响应。不应传递所有未询问的数据
      • 是的,这是一个很好的方法,但是当资源限制上下文下降时,我们无法进行更多预订,不是吗?您如何处理这些东西?
      • “下来”是什么意思?
      猜你喜欢
      • 2016-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-25
      • 2011-09-24
      • 1970-01-01
      • 1970-01-01
      • 2011-09-18
      相关资源
      最近更新 更多