验证跨越多个聚合根的域规则

Validation of domain rules spanning multiple aggregate roots

假设我们有以下 AR:

public class Service : Entity, IAggregateRoot
{
   public bool Available { get; private set; }
   public int TimeSlotDuration { get; private set; }
}

public class DaySchedule : Entity, IAggregateRoot
{
   public DayOfWeek DayOfWeek { get; }
   public List<Appointment> WorkingHours { get; private set; }
   public List<Appointment> Appointments { get; private set; }
}

public class Appointment : Entity
{
   public Guid ServiceId { get; }
   public int AppointmentStart { get; private set; }
   public int AppointmentEnd { get; private set; }
}

为了正确创建和定义约会,约会持续时间(约会开始和约会结束之间的时间)不得超过服务 AR 上定义的 TimeSlotDuration 值,而且服务必须可用。

现在,我面临的问题是 - 如何正确有效地执行此操作?

我现在正在做的事情如下:

  1. 读取服务 AR,获取并提取必要的值。
  2. 验证新约会的属性是否有效,否则抛出异常。

我想这行得通,但有没有更好的方法?如果我有 n 个其他 AR 需要检查怎么办?

我想到的是最终一致性,但我看不出如何在我的情况下应用它。也许某种 Saga 遍历所有聚合,验证所有域规则,一旦它们全部被验证,然后才插入约会?

Now, the problem that I'm facing is - how do I correctly and efficiently enforce this?

聚合 101:如果您有两条信息必须始终一致,那么这两条信息应该是同一聚合的一部分,因为任何可能改变一个的交易也必须考虑另一个。

在系统处于平衡状态时必须一致但在事情发生变化时不一定一致的两条信息可以分布在两个不同的集合中。

所以这是您必须解决的第一件事 - 您的约会是否需要始终与服务状态保持一致?或者就在事情停止移动的时候?

(在大多数涉及人类的情况下,商业答案是“当事物停止移动时”。)

如果您需要即时协议,那么任何时候您需要更改预约,您都需要防止服务“同时”更改,并且任何时候您对服务进行更改,您还需要考虑所有受影响的约会。

这反过来意味着 (a) 您只有一个锁,所有更改都会通过,或者 (b) 服务和受影响的约会始终锁定在一起。

我们组织相关信息以便将它们全部锁定在一起的模式的名称是“聚合”。

当您处于这种情况时,数据始终必须保持一致,那么推荐的做法是重新设计您的聚合,以便单个聚合可以保持不变。这通常会导致以您意想不到的方式分割您的领域模型——请参阅 Mauro Servienti 的演讲 All Our Aggregates are Wrong

最终一致性适用于在事物发生变化时数据不需要保持一致的情况,只要事物不断变化直到数据保持一致即可。一个共同的出发点是检测可能的问题,将关注升级到人员以进行补救。

查看 Rinat Abdullin 关于 Pat Helland 的 evolving process managers, and Memory, Guesses, and Apologies 的工作。

一般来说,我的处理方法是必须提供聚合需要但它不需要的任何数据。

integration/application 关注的是您收集所有数据和域对象的位置。通常您不会查询聚合,但获取数据的方式取决于您。一旦确定您确实可以根据 Service 的可用性和持续时间预订 Appointment,那么您就可以继续了。您可以选择在应用程序项目中创建一些 class 来封装 re-use.

的处理

可能 出现的“问题”是当您创建约会时服务持续时间and/or 可用性发生变化。这有点竞争条件。但是,可以通过为服务设置一个未来的 ExpiryDate 来缓解这种情况,或者如果服务属性发生变化,则为每个需要更改的 Appointment 启动一些补偿机制。这是流程管理器可能有用的地方。

您将 运行 进入许多依赖聚合外部甚至 BC 外部数据的场景。您需要根据您的要求设计每种方法。例如,航班预订可以通过先预订座位然后在预订被接受后应用预订来处理,或者允许对同一座位进行多次预订,先完成预订的人获胜。不过这是一种设计选择。