我正在阅读弗农的文章 Effective Aggregate Design .我有一个问题,为什么每个事务只修改一个聚合实例?
让我们举一个例子,考虑一个仓库库存管理的故事。
库存表示仓库中具有数量的项目。 5 Implementing Domain Driven Design以上海仓库的图书为例。
条目 表示有关 上的输入/输出操作的日志库存 .进入2 Implementing Domain Driven Design以上海仓库的图书为例。
安 库存如果 需要更改数量条目 被提交。
我很容易想到,这是一个可以通过事务一致性实现的不变量。
解决方案 A :使用一个聚合和集群 条目 进入 库存 .
public class Inventory implements Aggregate<Inventory> {
private InventoryIdentity id;
private Sku sku;
private int quantity;
private List<Entry> entries;
public void add(Entry entry) {
this.quantity += entry.getQuantity();
this.entries.add(entry);
}
}
public class Entry implements LocalEntity<Entry> {
private int quantity;
// some other attributes such as whenSubmitted
}
public class TransactionalInventoryAdminService impelments InventoryAdminService, ApplicationService {
@Override
@Transactional
public void handle(InventoryIdentity inventoryId, int entryQuantity, ...other entry attributes)
Inventory inventory = inventoryRepository.findBy(inventoryId);
Entry entry = inventory.newEntry(entryQuantity, ..);
inventory.add(entry);
inventoryRepository.store(inventory);
}
}
解决方案 B :对 使用单独的聚合库存和 条目 .
public class Inventory implements Aggregate<Inventory> {
private InventoryIdentity id;
private Sku sku;
private int quantity;
public void add(int quantity) {
this.quantity += quantity;
}
}
public class Entry implements LocalEntity<Entry> {
private Inventory inventory;
private int quantity;
private boolean handled = false;
// some other attributes such as whenSubmitted
public void handle() {
if (handled) {
throw .....
} else {
this.inverntory.add(quantity);
this.handled = true;
}
}
}
public class TransactionalInventoryAdminService impelments InventoryAdminService, ApplicationService {
@Override
@Transactional
public void handle(InventoryIdentity inventoryId, int entryQuantity, ...other entry attributes)
Inventory inventory = inventoryRepository.findBy(inventoryId);
Entry entry = inventory.newEntry(entryQuantity, ..);
entry.handle();
inventoryRepository.store(inventory);
entryRepository.store(entry);
}
}
A 和 B 都是可行的,但解决方案 B 有点不优雅,因为它会无意中留下调用 的机会。库存 .add(数量) 没有 条目 涉及。 这是规则(每个事务只修改一个聚合实例)试图为我指出的吗?我很困惑为什么我们应该只修改一个事务中的一个聚合,如果我们不修改会出现什么问题。
Update1 开始
它是否打算缓解并发问题(使用“制作更小的聚合”的另一条规则)?例如,条目 是一个竞争相对较低的聚合和 库存是一个竞争相对较高的(假设多个用户可以操作一个 Inventory ),如果我在一个事务中修改它们会导致不必要的并发失败。
Update1 结束
如果采用方案A,还需要解决一些问题:
1.如果有很多怎么办条目 s 代表 库存我需要一个分页查询 UI?如何使用集合实现分页查询?一种方法是加载所有 条目 s 并选择页面需要的内容,另一种方式是 InventoryRepository.findEntriesBy(invoiceId, paging),但这似乎打破了仅通过获取本地实体然后导航对象图来获取本地实体的规则。
2.如果太多怎么办条目 s 代表 库存我必须在添加新的 时加载所有这些文件条目 ?
我知道这些问题源于缺乏充分的理解。所以任何想法都是受欢迎的,提前致谢。
最佳答案
经验法则是保持聚合较小,因为您希望避免由于并发而导致的事务失败。如果不应该,我们为什么要使内存占用很大?
因此,解决方案 A 不是最优的。大集合通常会带来很容易避免的问题。
确实,另一个经验法则是在一个事务中只更改一个聚合。如果您将 Entry 设为自己的聚合,则可以使库存的数量最终保持一致,这意味着 Entry 聚合可能引发订阅库存的事件。通过这种方式,您只需更改每笔交易的一个聚合。
public class Entry {
public Entry(InventoryId inventoryId, int quantity) {
DomainEvents.Raise(new EntryAdded(inventoryId, quantity))
}
}
如果您对最终的一致性感到不舒服,您仍然可以将聚合分开,但暂时在一个事务中修改它们 - 直到您感到痛苦,使用封装域服务。另一种选择是保持域事件的进程,以便它们也在单个事务中提交。
public class InventoryService {
public void AddEntryToInventory(Entry entry) {
// Modify Inventory quantity
// Add Entry
}
}
关于domain-driven-design - 为什么每个事务只修改一个聚合实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17758596/