【问题标题】:Why modifies only one aggregate instance per transaction?为什么每个事务只修改一个聚合实例?
【发布时间】:2013-07-20 04:16:16
【问题描述】:

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

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

Inventory 表示仓库中有数量的物品。以上海仓库5Implementing Domain Driven Design图书为例。

Entry 表示关于Inventory 的输入/输出操作的日志。以上海仓库入库2Implementing Domain Driven Design图书为例。

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

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

解决方案 A:使用一个聚合并将 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:对 InventoryEntry 使用单独的聚合。

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)。 这是规则(每个事务只修改一个聚合实例)试图为我指出的吗?我很困惑为什么我们应该只修改事务中的一个聚合,如果我们不这样做会出现什么问题。

Update1 开始

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

更新 1 结束

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

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

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

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

【问题讨论】:

标签: domain-driven-design


【解决方案1】:

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

因此,解决方案 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
     }
 }

【讨论】:

  • +1 感谢您的回复。我很困惑为什么我们应该只修改事务中的一个聚合,如果我们不这样做会出现什么问题。
  • :) 我在企业集成模式中学到了这个词。如果我没记错的话,“一些工具可以帮助缓解这个问题”。我也在学英语。
  • “另一种选择是让域事件保持在进程中,以便它们也在单个事务中提交。”好的,但这仍然违反了每笔交易修改一个聚合的法律,或者我不明白你的线索@JefClaes?
  • 您能否更详细地解释为什么存在这种“每次交易一个聚合”的经验法则?您似乎暗示这与“生产中的事务失败”有关,但我很想知道我们可能会遇到什么样的失败。以及它们是否超过了最终一致性的潜在风险(例如,因为库存保持一致为时已晚而将一件商品出售两次)。
  • @JefClaes 关于真正一致性的有趣点 - 尽管请记住,有许多纯数字系统不涉及此类模拟案例。关于您关于每笔交易的多个聚合的示例:我正在寻找一个更现实的示例。当然,购买不会改变产品定义。我见过许多实例,其中事务插入一个聚合并更新另一个聚合,其中根本没有后者从另一个进程获得冲突更新的现实场景。因此,尽管在某些情况下这可能是个问题,但通常不是。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 2016-06-11
    • 1970-01-01
    相关资源
    最近更新 更多