我有一个Contract实体,该实体具有DateRange(dateFrom,dateTo)属性和一个Sales集合。

每个Sale还具有一个DateRange属性,该属性必须在ContractDateRange的边界内。

更改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是聚合根,而SaleContract聚合内的实体。由于要求任何销售日期都必须在一组合同日期之内,因此对销售日期的任何更改都必须由合同管理,以便它可以首先检查合同日期。

为此,您将在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/

10-12 06:44