【问题标题】:DDD one transaction per aggregate - tracking caseDDD 每个聚合一个事务 - 跟踪案例
【发布时间】:2019-05-16 16:18:23
【问题描述】:

假设我有两个聚合 A 和 B。我在 A 上使用工厂方法来创建 B。我还要求 A 不能产生超过 x 个 B 实例。

下面的实现看起来很自然:

A.createB() {

  if (total> x) 
        raise an error

  total++
  return new B()
}

但这会违反修改两个聚合的规则:创建 B 和修改 A。

如果我尝试遵守此规则,我会: 1. 在 A 中创建 B,并引发类似 BCreated 的事件。 2. 通过处理 BCreated 事件更新 A 在下一笔交易中的总计数。

对我来说,在这个特定的例子中,这看起来像是一个奇怪的解决方法,因为在 A 上调用 createB() 方法后,我让它处于不一致的状态。

我错过了什么吗?

【问题讨论】:

    标签: oop domain-driven-design


    【解决方案1】:

    为简单起见,我会将这些汇总更改视为一个工作单元。唯一的建议是您必须处理竞争条件。 现在,如果您想以最终解决方案为例,您必须对 A 和 B 更改的事务建模聚合建模:BCreation A.requestBCreation 更改 A 状态并发出事件 (BCreationAllowed) BCreation 做出反应,然后 BCreation 调度命令以创建 B 并处理其后果域事件 BCreated 比方说,或 BCreationRejected。聚合 BCreation 监听任何这些事件等等。这可能是一个复杂且设计过度的解决方案。您还必须处理竞争条件,因此“同步”流程聚合。 如果您使用 Actor 模型,一切都会变得容易得多。

    【讨论】:

      【解决方案2】:

      我错过了什么吗?

      你没有错过任何东西简单,不。

      您面临的问题的通用术语是set validation -- 如果您要对一组数据保持不变,那么修改该数据的所有操作都必须通过同一个锁。

      当整个世界都是一个关系数据库时,该锁可能是隐式的——数据库本身正在以某种(逻辑上)序列化顺序处理所有事务,因此请注意保持不变式,因为,在存储级别,每笔交易都是全有或全无。

      但是,如果您将这些数据分布在 两个 数据库中,那么所有的赌注都将失败。

      另一种思考方式:如果您的事务只有在所有不同的“聚合”都存储在同一个数据库中时才能工作,这表明您真正拥有的是更大的聚合,隐含在您的实现中细节——而且扩展成本会更高。

      通常,我们可以稍微放松不变量——尽最大努力维护不变量,同时检测违规行为并定义协议进行补偿。

      【讨论】:

        【解决方案3】:

        虽然“每笔交易一个聚合”是一个规则,但它可能不会杀死你务实并在某些情况下忽略它。事实上,我认为在某些情况下它是不切实际的,甚至不可能通过任何其他方式获得。

        话虽如此,您绝对应该尽最大努力遵守该准则。你的情况并不少见。例如,库存水平和机票(实际上也是库存水平)浮现在脑海中。

        将操作分成两个不同步骤的唯一方法是跟踪流程。为此,您需要一个流程管理器,甚至可能需要一些消息传递,但这都是管道。

        要解决这个问题,您需要在第一步中“保留”创建,例如使用一些相关标识符。然后可以将其保存在事务 A 中:

            // begin tx (application layer)
            if (A.Reserve(id))
            {
                // we're good
                bus.Send(new RegisterBCommand
                         {
                            Id = id,
                            TheIdForA = theId
                            // other properties
                         }); // perhaps using Shuttle.Esb
            }
            // commit tx (application layer)
        

        下一步将注册 B 实体,并可能发布 BRegisteredEvent 以继续该过程。

        还有一点:如果 A 和 B 都生活在同一个有界上下文中,您通常只会有 A.CreateB()。实现稍微相似的另一种方法是使用集成有界上下文(例如您的编排 BC),然后将 CreateB() 作为 A 上的扩展方法,其中 A 和 B 位于单独的 BC 中,但编排层使用两个域.另一条路线是普通工厂或只是在您的应用程序/域服务中新建它。

        【讨论】:

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