我正在阅读弗农的文章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开始
是否打算减轻并发问题(另一个规则是“制作更小的聚合”)?例如,条目是竞争相对较低的聚合,广告资源是竞争相对较高的聚合(假设多个用户可以操纵一个广告资源)如果我在事务中修改它们,它会导致不必要的并发失败。
Update1结束
如果我采用解决方案A:
,还需要解决一些其他问题1.如果广告资源有很多条目,那么我需要一个分页查询用户界面?如何使用集合实现分页查询?一种方法是加载所有条目并选择页面需要的内容,另一种方法是InventoryRepository.findEntriesBy(invoiceId,paging),但这似乎打破了获取本地实体的规则它是aggreate然后导航对象图。
2.如果广告资源的条目太多,我必须在添加新条目时加载所有内容 ?
我知道这些问题源于缺乏充分理解。所以任何想法都是受欢迎的,提前谢谢。
答案 0 :(得分:11)
经验法则是保持聚合较小,因为您希望避免由于并发而导致的事务性失败。如果它不应该是为什么我们会使内存占用大?
因此,解决方案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
}
}
答案 1 :(得分:0)
应避免在单个事务中修改多个聚合的原因之一是,每个聚合可能存储在不同的数据库存储中,并且可能需要某些特定的事务处理或施加管理分布式事务的所有困难(两阶段提交等)
更好的方法是最终的一致性以及事件和传奇模式。