首页 > 解决方案 > 跨多个聚合根的域规则验证

问题描述

假设我们有以下 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; }
}

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

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

我目前正在做的事情如下:

  1. 阅读 Service AR,获取并提取必要的值。
  2. 验证新约会上的属性是否有效,如果不是,则抛出异常。

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

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

标签: c#domain-driven-design

解决方案


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

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

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

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

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

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

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

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

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

最终一致性适用于在事情发生变化时数据不需要保持一致的情况,只要事情一直在变化,直到数据保持一致。一个共同的出发点是检测一个可能的问题,将问题上报给人们进行补救。

请参阅 Rinat Abdullin 关于不断发展的流程管理器的工作,以及Pat Helland 的Memory、Guesses 和 Apologies


推荐阅读