c# - DDD 在子实体更改状态时强制执行聚合不变量

标签 c# domain-driven-design

我有一个 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)日期内,因此对销售日期的任何更改都必须由契约(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/

相关文章:

c# - 所见即所得 View 创建的 WPF 设计器限制和规则摘要?

c# - 在 ASP.NET MVC 中使用参数在另一个 View 中重用一个 View

C#如何读取\r\n字符串

entity-framework - 如何避免规则形式的业务逻辑贫乏的领域模型

domain-driven-design - 具有重要不变量的潜在大型集合的 DDD 聚合

c# - 防止对象进入某种状态后被更改? (C#)

c# - 如何将 IEnumerable<IEnumerable<T>> 转换为 List<List<T>>

c# - Count=1 的此 SqlParameterCollection 的索引 1 无效

asp.net - 将存储库模式与 Entity Framework (mvc 店面)一起使用

domain-driven-design - 在 DDD 中将 IQueryable 与存储库模式结合使用