DDD在子实体改变状态时强制聚合不变量
本文关键字:不变量 状态 实体 改变 DDD | 更新日期: 2023-09-27 18:12:48
我有一个Contract
实体,它具有DateRange
(dateFrom, dateTo)属性和Sales
集合。
每个Sale
也有一个DateRange
属性,必须在Contract
的DateRange
的边界内。
当更改Sale
的日期时,执行上述不变量的正确方法是什么?
public class Contract : Entity
{
public DateRange Dates { get; private set; }
public ICollection<Sale> Sales { get; private set; }
}
public class Sale : Entity
{
public DateRange Dates { get; private set; }
public void ChangeDates(DateRange dates)
{
Dates = dates;
}
}
编辑
Contract
日期可以随时更改,因此每个Sale
都应进行相应的修改。
基于当前需求
解释您的需求,Contract
是聚合根,Sale
是Contract
聚合中的一个实体。由于要求任何销售日期必须在一组合同日期内,因此任何对销售日期的更改都必须由合同管理,因此它可以首先检查合同日期。
Contract
上有一个方法,如:
public void ChangeSaleDate(long SaleId, DateRange dates)
{
if (this.Dates.Surround(dates))
{
var sale = this.Sales.First(s => s.Id == SaleId);
sale.ChangeDates(dates);
}
else
{
throw new ArgumentException("New Sale dates must be between ...", "dates");
}
}
这假设你有一个SaleId
-或其他方式来识别合同中的销售,并且你已经在DateRange
上实现了一个Surround
方法来支持这种检查。
根据您的项目结构,您还可以将Sale
上的ChangeDates
方法标记为internal
,以确保您不会意外地从应用程序服务调用它。
从你的评论来看,这是真的,这种机制可能导致在聚合根(Contract
)上产生大量方法,因为它强制执行适用于合同中"所有"销售的不变量。因此,像这样的情况可以提示挑战需求…
挑战需求
DDD有助于聚合之间的"最终一致性"——因为聚合定义了一个一致性边界,如果你想定义一个跨越边界的规则,你必须接受这个规则可能并不总是适用。
另一种实现是使Sale
成为自己的聚合。在这种情况下,您不会在Contract
上拥有ICollection<Sale>
属性,而只是在Sale
上拥有ContractId
属性,并且每个销售都将获得自己的全局唯一标识符。
然而,这种技术的可行性取决于合同日期是否允许更改,以及更改后应该发生什么……说明:
要更改销售日期,您将使用ContractRepository
获得Contract
,使用SaleRepository
获得Sale
,并可能将合同传递给Sale
上的日期更改方法:
public void ChangeDate(Contract contract, DateRange dates)
{
if (contract.Id != this.ContractId)
throw new ArgumentException("wrong contract", "contract");
if (!contract.AreSaleDatesValid(dates))
throw new ArgumentException("wrong dates", "dates");
this.Dates = dates;
}
这里的风险,因为你的合同和销售在交易上不一致,取决于合同日期是否可以改变。
如果没有,那么这种方法是简单可行的,并确保您可以直接访问Sales。
然而,如果他们可以,那么风险是合同日期可能在您更改销售日期的同时更改,因此您的规则将暂时被打破。
但是,这就是域事件可以提供帮助的地方。如果您的Sale.ChangeDate
方法发布了一个事件SaleDatesChanged
,并且您在新事务中异步处理该事件,则处理程序可以检查Sale日期是否仍然对合同有效。
接下来会发生什么取决于您的业务需求-提醒手动审查,还是自动更改销售日期以适应新的合同日期?
同样,Contract.ChangeDate
方法将发布ContractDatesChanged
,处理程序将检查所有销售是否在合同日期内,并再次发出警报或进行调整。
这是DDD要求的"最终一致性"——你的所有销售必须在合同日期内完成的规则将得到满足……最终.
这就是为什么我说"挑战"需求——如果在这些情况下,允许销售日期超出合同日期并以适当的商业方式处理它真的会更好,那么你已经挑战了你的需求,并对该领域有了更深入的了解。