12

我正在阅读 Vernon 的文章Effective Aggregate Design。我有一个问题,为什么每个事务只修改一个聚合实例?

让我们举个例子,考虑一个仓库库存管理的故事。

库存表示仓库中具有数量的项目。5以上海仓库实施领域驱动设计书籍为例。

条目表示有关Inventory上的输入/输出操作的日志。以上海仓库为例,进入2本实现领域驱动设计的书籍。

如果提交条目,则需要更改Inventory的数量。

我很容易想到,这是一个不变量,可以通过事务一致性来实现。

解决方案 A:使用一个 Aggregate 和 cluster Entry进入Inventory

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 有点不雅,因为在不涉及Entry的情况下会无意中调用Inventory .add(quantity) 。这是规则(每个事务只修改一个聚合实例)试图为我指出的吗?我很困惑为什么我们应该只修改事务中的一个聚合,如果我们不这样做会出现什么问题。

更新1开始

它是否打算缓解并发问题(使用“制作更小的聚合”的另一条规则)?例如,Entry是竞争相对较低的 Aggregate,而Inventory是竞争相对较高的聚合(假设多个用户可以操作一个Inventory),如果我在事务中同时修改它们,则会导致不必要的并发失败。

更新1结束

如果我采用解决方案A,还需要解决一些进一步的问题:

1.如果一个Inventory有很多Entry,我需要一个分页查询UI怎么办?如何使用集合实现分页查询?一种方法是加载所有Entry并选择页面需要的内容,另一种方法是 InventoryRepository.findEntriesBy(invoiceId, paging),但这似乎打破了仅通过获取本地实体来获取本地实体的规则,然后导航对象图.

2.如果一个Inventory的Entry太多,我必须在添加新Entry时加载所有这些怎么办?

我知道这些问题源于缺乏充分的理解。所以欢迎任何想法,在此先感谢。

4

2 回答 2

13

经验法则是使您的聚合保持较小,因为您希望避免由于并发导致的事务失败。如果不应该,我们为什么要让内存占用很大?

因此,解决方案 A 不是最优的。大聚合通常会引入很容易避免的问题。

确实,另一条经验法则是在一个事务中只更改一个聚合。如果您将条目设为自己的聚合,则可以使库存的数量最终保持一致,这意味着条目聚合可以引发订阅库存的事件。这样,您只需更改每个事务的一个聚合。

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
     }
 }
于 2013-07-20T11:51:07.330 回答
0

您应该避免在单个事务中修改多个聚合的原因之一是每个聚合可能存储在不同的数据库存储中,并且可能需要一些特定的事务处理或施加管理分布式事务(两阶段提交等)的所有困难。

更好的方法是最终与事件和传奇模式保持一致。

另请参阅:https ://softwareengineering.stackexchange.com/questions/356106/ddd-why-is-it-a-bad-practice-to-update-multiple-aggregate-roots-per-transaction

于 2019-08-03T16:56:27.480 回答