DDD聚合一个聚合中实体之间的一对一些而不是一对多的边界



我看过一个关于DDD的教程,其中说,如果我有聚合根SnackMachine,它有30多个子元素,那么子元素应该在单独的聚合中。例如,SnackMachine有很多PurshaseLog(超过30个(,PurshaseLog最好是单独的聚合。为什么?

限制聚合的总体大小的原因是,您总是将完整的聚合加载到内存中,并且总是以事务方式存储完整的聚合。一个非常大的骨料会造成技术问题。

也就是说,不存在这样的";30个子元素";总体设计中的规则,作为一条规则,这听起来很武断。例如,从技术上讲,具有更少的非常大的子元素可能比具有30个非常轻的子元素更糟糕。将聚合存储为json文档是一种很好的方法,因为您总是将文档作为原子操作进行读写。如果你这样想,你会意识到,一个包含大量甚至不断增长的儿童收藏的总体设计最终会带来问题。PurhaseLog听起来像是一个不断增长的集合。

规则的第二部分说";把它放在一个单独的集合中";也不正确。您不创建聚合,因为您需要存储一些数据,而且这些数据不适合现有的聚合。您创建聚合是因为您需要实现一些业务逻辑,而这个业务逻辑需要一些数据,所以您将这两个东西放在一个聚合中。

因此,尽管你在问题中解释的是设计骨料时要考虑的问题,以避免出现技术问题,但我建议你注意骨料的实际责任。

在您的示例中,SnackMachine的职责是什么?它真的需要(完整的(PurchaseLogs列表吗?SnackMachine将暴露哪些操作?假设它公开了PurchaseProduct(productId(和LoadProduct(productId,quantity(。为了执行其业务逻辑,该聚合需要一个产品列表并统计其可用数量,但不需要存储购买日志。相反,在每次购买时,它都可以发布一个事件ProductPurchase(SnackMachineId、ProductId、Date、AvailableQuantity(。然后外部系统可以订阅此事件。一个用户可以注册PurchaseLog进行报告,另一个用户则可以在库存低于X.时派人重新加载机器

如果PurchaseLog不是它自己的聚合,则意味着它只能作为SnackMachine的子集合的一部分进行检索或添加。

因此,每次您想要添加PurchaseLog时,您都会检索SnackMachine及其子PurchaseLogs,并将PurchaseLlog添加到其集合中。然后保存对工作单元的更改。

您是否真的需要检索30多个购买日志,这些日志对于创建新购买日志的用例来说是多余的?

应用层-选项1(PurchaseLog是SnackMachine的一个拥有实体(

// Retrieve the snack machine from repo, along with child purchase logs
// Assuming 30 logs, this would retrieve 31 entities from the database that
// your unit of work will start tracking.
SnackMachine snackMachine = await _snackMachineRepository.GetByIdAsync(snackMachineId);
// Ask snack machine to add a new purchase log to its collection
snackMachine.AddPurchaseLog(date, quantity);
// Update
await _unitOfWork.SaveChangesAsync();

应用层-选项2(PurchaseLog是聚合根(

// Get a snackmachine from the repo to make sure that one exists
// for the provided id.  (Only 1 entity retrieved);
SnackMachine snackMachine = await _snackMachineRepository.GetByIdAsync(snackMachineId);
// Create Purhcase log
PurchaseLog purchaseLog = new(
snackMachine,
date,
quantity);
await _purchaseLogRepository.AddAsync(purchaseLog);
await _unitOfWork.SaveChangesAsync()

采购日志-选项2

class PurchaseLog
{
int _snackMachineId;
DateTimne _date;
int _quantity;
PurchaseLog(
SnackMachine snackMachine,
DateTime date,
int quantity)
{
_snackMachineId = snackMachine?.Id ?? throw new ArgumentNullException(nameof(snackMachine));
_date = date;
_quantity = quantity;
}
}

第二个选项更准确地遵循用例的轮廓,也减少了数据库的i/o。

最新更新