问题描述
在有关聚合的章节中有关ddd的文章/书籍中,我感到从未讨论过一个主题:
如何重新加载聚合?
- 这太明显了,谈论它没用
- 这不是正确的选择
- 我听不懂
如果我以这些项目的顺序作为示例,则创建或更新的不变式是相同的。 假设我必须更新已有订单的商品价格,所以我将从重新加载汇总开始。
但是我该怎么做?
-
使用诸如automapper这样的工具将能够设置私有属性?
解决方法
从头开始逐项重建所有内容,就像我正在创建一个新的东西一样?
通常是这个。
使用适当的通用机制从持久性存储中加载数据:字节数组,json文档,记录集等。现在您已将信息存储在内存中,然后将该信息复制到生成域模型对象的工厂中。
通常,您的设计中会包含某种工厂模式的体现,该工厂模式会根据其他信息创建汇总-您通常可以在存储库的外观后面使用类似的工厂,以在重新加载时获得相同的结果。
,您询问有关重新加载聚合的系统,但是该系统也应该能够创建和更新聚合。创建它们时,要避免重复。更新它们时,您要以事务方式保存整个聚合,并且要避免并发更新在数据库中留下不一致的状态。考虑两个单独的线程,每个线程为同一产品添加一个订单项,并且您不允许重复的产品。每个线程的顺序是一致的,但是如果您不为此做准备,则可以在关系数据库中最终插入重复的订单项。
您还希望将核心代码与数据访问代码隔离。为此,我通常使用以下界面:
public interface IOrderUnitOfWork
{
Task<Order> GetOrCreate(Guid id,Func<Task<Order>> createFunc);
Task<Order> Get(Guid id);
Task Commit();
}
其工作原理如下:
-
对于需要创建聚合的用例,我使用
GetOrCreate
。这样做的原因是使操作成为幂等。首次调用该操作时,它将调用createFunc
。如果重试该操作,它将找到订单,并且不会再次创建该订单。 -
对于汇总应该已经存在的用例(添加订单项,如果没有订单就无法使用),我使用
Get
。如果Order不存在,则将抛出NotFoundException。这使您的代码清理器在用例中节省了数百个空检查。 -
在每个用例的结尾,我总是致电
Commit
。 UnitOfWork应该跟踪它加载的聚合,并且知道是否进行了更改。如果发生了更改,它将以事务方式保存整个聚合,并进行乐观的并发检查,因此,如果另一个进程更改了数据库中的聚合,则提交将失败。
现在,您可以针对此接口实施用例,而无需考虑用于实现该接口的技术。我已经使用了ORM(实体框架)和基于json的数据存储。将聚合存储为文档具有以下优点:默认情况下,它是事务性/原子性的。如果将关系数据库与ORM一起使用,则可以使ORM将聚合直接映射到数据库,因此不必在数据访问层中创建所有类的副本,然后使用Automapper或手动映射DAO对象到您的核心对象,因为这将意味着大量的代码不会增加任何价值。
您还可以考虑使用事件源,因此将聚合状态存储为一系列事件,这些事件顺序应用以构造聚合的当前状态(OrderCreated,LineItemAdded,LineItemAdded,LineItemChanged等)。在这种情况下,工作单元的实现将加载事件,并且您的集合将具有某种ApplyEvents方法来构造当前状态。