我有一个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
-或通过其他方式识别合同中的销售,并且已在Surround
上实现了DateRange
方法以支持这种检查。根据您的项目结构,还可以将
ChangeDates
上的Sale
方法标记为internal
,以确保不会意外地从应用程序服务中调用它。从您的评论来看,的确是这样,这种机制可以在聚合根(
Contract
)上导致大量方法,因为它强制执行适用于合同中“所有”销售的不变量。结果,这样的情况可能会提示您挑战需求...挑战要求
DDD有助于聚合之间的“最终一致性”-聚合定义了一个一致性边界,如果您想定义一个越过边界的规则,则必须接受该规则可能并不总是适用。
另一种实现方式是使
Sale
成为自己的集合。在这种情况下,您将在ICollection<Sale>
上没有Contract
属性-而是在ContractId
上仅具有Sale
属性,并且每个销售都将获得自己的全局唯一标识符。但是,此技术的可行性取决于是否允许更改合同日期,以及在更改合同日期时会发生什么……以说明:
要更改销售日期,可以使用
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
,并且您在新事务中异步处理了该事件,则处理程序可以检查销售日期对于该合同是否仍然有效。接下来会发生什么取决于您的业务需求-提醒进行人工审核,还是自动更改销售日期以适合新合同日期?
同样,
Contract.ChangeDate
方法将发布ContractDatesChanged
,为此处理程序将检查所有销售是否在合同日期之内,并再次发出警报或进行调整。这是DDD要求的“最终一致性”-最终将满足您所有销售必须在合同日期之内的规则。
这就是为什么我说“挑战”要求的原因-如果在这种情况下,允许销售日期超出合同日期并以合适的业务方式处理确实更好,那么您就挑战了您的要求并制定了一个更深入地了解该领域。
关于c# - 当子实体更改状态时,DDD强制执行聚合不变量,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40015443/