当子实体改变状态时,DDD强制执行聚合不变量

时间:2016-10-13 08:01:58

标签: c# domain-driven-design

我有一个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

1 个答案:

答案 0 :(得分:3)

根据您当前的要求

解释您的要求,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)上的大量方法,因为它强制执行适用于所有&#39;所有&#39的不变量;在合同中销售。因此,像这样的情况可能会提示挑战要求......

挑战要求

DDD有助于最终的一致性和#39;聚合之间 - 作为聚合定义一致性边界,如果要定义跨越边界的规则,则必须接受该规则可能始终应用。

另一种实现方式是使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;
}

此处的风险,因为您的合约和销售在交易上不一致,取决于合约日期是否会发生变化。

如果没有,那么这种方法简单易行,并确保您可以直接访问销售。

但是,如果他们可以,则风险是合约日期可能会同时更改 您正在更改销售日期,因此您的规则将会暂时中断。 / p>

但是,这是域事件可能有所帮助的地方。如果您的Sale.ChangeDate方法发布了事件SaleDatesChanged,并且您在新事务中异步处理事件,则处理程序可以检查销售日期是否仍然对合同有效。

接下来会发生什么情况取决于您的业务需求 - 提醒人工审核,或自动更改销售日期以适应新合约日期?

同样,Contract.ChangeDate方法会发布ContractDatesChanged,此处理程序将检查所有销售是否在合同日期内,并再次发出警报或调整。

这是最终的一致性&#39;根据DDD要求 - 所有销售必须在合同日期内的规则将最终得到满足。

这就是为什么我说'挑战'&#39;要求 - 如果允许销售日期在这些情况下超出合同日期并以业务适当的方式处理它真的更好,那么您已经挑战了您的要求并形成了更深入的洞察力进入域名。