如何加载聚合?

问题描述

在有关聚合的章节中有关ddd的文章/书籍中,我感到从未讨论过一个主题

如何重新加载聚合?

  • 这太明显了,谈论它没用
  • 这不是正确的选择
  • 我听不懂

如果我以这些项目的顺序作为示例,则创建或更新的不变式是相同的。 假设我必须更新已有订单的商品价格,所以我将从重新加载汇总开始。

但是我该怎么做?

  1. 使用诸如automapper这样的工具将能够设置私有属性

  2. 使用一个构造函数,该构造函数采用根聚合的ID,子存储库并在此构造函数中分配private属性

  3. 从头开始逐项重建所有内容,就像我正在创建一个新的东西一样?

解决方法

从头开始逐项重建所有内容,就像我正在创建一个新的东西一样?

通常是这个。

使用适当的通用机制从持久性存储中加载数据:字节数组,json文档,记录集等。现在您已将信息存储在内存中,然后将该信息复制到生成域模型对象的工厂中。

通常,您的设计中会包含某种工厂模式的体现,该工厂模式会根据其他信息创建汇总-您通常可以在存储库的外观后面使用类似的工厂,以在重新加载时获得相同的结果。

,

您询问有关重新加载聚合的系统,但是该系统也应该能够创建和更新聚合。创建它们时,要避免重复。更新它们时,您要以事务方式保存整个聚合,并且要避免并发更新在数据库中留下不一致的状态。考虑两个单独的线程,每个线程为同一产品添加一个订单项,并且您不允许重复的产品。每个线程的顺序是一致的,但是如果您不为此做准备,则可以在关系数据库中最终插入重复的订单项。

您还希望将核心代码与数据访问代码隔离。为此,我通常使用以下界面:

public interface IOrderUnitOfWork
{
    Task<Order> GetOrCreate(Guid id,Func<Task<Order>> createFunc);
    Task<Order> Get(Guid id);
    Task Commit();
}

其工作原理如下:

  1. 对于需要创建聚合的用例,我使用GetOrCreate。这样做的原因是使操作成为幂等。首次调用该操作时,它将调用createFunc。如果重试该操作,它将找到订单,并且不会再次创建该订单。

  2. 对于汇总应该已经存在的用例(添加订单项,如果没有订单就无法使用),我使用Get。如果Order不存在,则将抛出NotFoundException。这使您的代码清理器在用例中节省了数百个空检查。

  3. 在每个用例的结尾,我总是致电Commit。 UnitOfWork应该跟踪它加载的聚合,并且知道是否进行了更改。如果发生了更改,它将以事务方式保存整个聚合,并进行乐观的并发检查,因此,如果另一个进程更改了数据库中的聚合,则提交将失败。

现在,您可以针对此接口实施用例,而无需考虑用于实现该接口的技术。我已经使用了ORM(实体框架)和基于json的数据存储。将聚合存储为文档具有以下优点:默认情况下,它是事务性/原子性的。如果将关系数据库与ORM一起使用,则可以使ORM将聚合直接映射到数据库,因此不必在数据访问层中创建所有类的副本,然后使用Automapper或手动映射DAO对象到您的核心对象,因为这将意味着大量的代码不会增加任何价值。

您还可以考虑使用事件源,因此将聚合状态存储为一系列事件,这些事件顺序应用以构造聚合的当前状态(OrderCreated,LineItemAdded,LineItemAdded,LineItemChanged等)。在这种情况下,工作单元的实现将加载事件,并且您的集合将具有某种ApplyEvents方法来构造当前状态。