我有一个 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)日期内,因此对销售日期的任何更改都必须由契约(Contract)管理,因此它可以首先检查契约(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
- 或在契约(Contract)中识别销售的其他方式,并且您已经实现了 Surround
DateRange
上的方法支持这种检查。
根据您的项目结构,您还可以标记 ChangeDates
Sale
上的方法作为internal
以确保您不会意外地从您的应用程序服务中调用它。
根据您的评论,此机制确实可以导致聚合根 (Contract
) 上的大量方法,因为它强制执行适用于契约(Contract)中“所有”销售的不变量。因此,像这样的情况可以提示挑战要求......
挑战要求
DDD 有助于聚合之间的“最终一致性”——因为聚合定义了一个一致性边界,如果你想定义一个跨越边界的规则,你必须接受规则可能不会总是申请。
另一种实现方式是使 Sale
自己的聚合体。在这种情况下,您不会有 ICollection<Sale>
Contract
上的属性(property)- 而你只是有一个 ContractId
Sale
上的属性(property),并且每次销售都会获得自己的全局唯一标识符。
但是,此技术的可行性取决于是否允许更改契约(Contract)日期,以及更改时应该发生什么……举例说明:
要更改促销日期,您可以使用 ContractRepository
得到 Contract
, 和 SaleRepository
得到 Sale
,并可能将契约(Contract)传递给 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;
}
这里的风险,因为您的契约(Contract)和销售在交易上不一致,取决于契约(Contract)日期是否可以更改。
如果没有,那么这种方法简单可行,并确保您可以直接访问销售。
但是,如果他们可以,那么风险是契约(Contract)日期可能会发生变化同时您正在更改销售日期,因此您的规则将 - 暂时 - 被打破。
但是,这正是领域事件可以提供帮助的地方。如果你的Sale.ChangeDate
方法发布了一个事件 SaleDatesChanged
并且您在新事务中异步处理事件,然后处理程序可以检查销售日期是否仍然对契约(Contract)有效。
接下来会发生什么取决于您的业务需求 - 提醒人工审核,还是自动更改销售日期以适应新的契约(Contract)日期?
同样,Contract.ChangeDate
方法将发布 ContractDatesChanged
处理程序将检查所有销售是否都在契约(Contract)日期内,并再次提醒或调整。
这是来自 DDD 要求的“最终一致性”——您关于所有销售必须在契约(Contract)日期内的规则将得到满足……最终。
这就是为什么我说“挑战”要求 - 如果在这些情况下允许销售日期超出契约(Contract)日期并以业务适当的方式处理它真的更好,那么您已经挑战了自己的要求,并对领域有了更深入的了解。
关于c# - DDD 在子实体更改状态时强制执行聚合不变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40015443/