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

Domain driven design 具有重要不变量的潜在大集合的DDD聚合,domain-driven-design,aggregate,invariants,Domain Driven Design,Aggregate,Invariants,我知道聚合应该是小的,它们应该保护不变量。 我还知道,在聚合中保留大型集合会影响性能 我有一个用例,需要保护它的不变量,但也会导致大量的收集 聚合是供应商,它可以有多个活动的促销。每个促销都有促销类型、开始日期和结束日期。不变量是: 在任何时间点,每个促销类型最多只能有一次促销 在任何时候,最多可以有2次促销 。。。但是在促销DomainService(以及返回的聚合)中保护它,我们冒着竞争条件和不一致性的风险(除非我们应用悲观锁) 在这种情况下,推荐的DDD方法是什么?这是一个有趣的案例,

我知道聚合应该是小的,它们应该保护不变量。 我还知道,在聚合中保留大型集合会影响性能

我有一个用例,需要保护它的不变量,但也会导致大量的收集

聚合是供应商,它可以有多个活动的促销。每个促销都有促销类型开始日期结束日期。不变量是:

  • 在任何时间点,每个促销类型最多只能有一次促销
  • 在任何时候,最多可以有2次促销
。。。但是在促销DomainService(以及返回的聚合)中保护它,我们冒着竞争条件和不一致性的风险(除非我们应用悲观锁)


在这种情况下,推荐的DDD方法是什么?

这是一个有趣的案例,因为我一直在为聚合根为什么需要实体而挣扎。我倾向于使用聚合中的值对象,这些值对象通过id引用其他聚合,但我认为这里可能有一个实体

一种解决方案可能是只能够在
供应商
中注册促销,从而强制执行不变量。
VendorRepository
只会加载活动促销并将其添加到
供应商
。这样,它们可以随时过期,但存储库只会加载相关的促销

对于开放式促销(您可能不一定会有),您甚至可以在
供应商
之外终止促销,您的不变量应该仍然满足


即使您将
促销作为一个有效的价值对象,您仍然可以遵循此方法。

您的聚合应该只包含实现其目的所需的数据。阅读您的问题描述,我不认为供应商需要任何过期的促销活动。因此,您只需在收藏中保留活动促销

在AddPromotion方法中,如果存在该类型的活动升级,您将返回一个错误。如果没有该类型的任何促销,您将添加它,如果该类型的促销过期,您将替换它。除非你有大量的促销类型(事实似乎并非如此),否则每种类型最多只能有一次促销。看来,这将保持收集到一个非常合理的规模。如果不是这样,请告诉我

您很可能需要过期的促销活动作为历史数据。但是,这些应该基于为此目的而设计的读取模型,而不是聚合。为此,聚合可以在接受新升级的每种类型上发布一个事件,侦听器将对该事件做出反应,并在历史升级表中插入一条记录

更新:


在再次阅读了这个问题后,我意识到你甚至不需要保持每种类型的促销活动。集合中最多有2次促销,因此集合的大小将最多为2,除非我有误解。

也许您的聚合是促销期?)如果AR是促销期,则您将无法保持不变量而不进行锁定(悲观或乐观)。您不能保留什么不变量?这两个不变量(“在任何时间点,每个促销类型最多只能有一次促销”和“在任何时间点,最多可以有两次促销”)。因此促销期可以保护这两个不变量。为什么不谢谢埃本。这是一个有趣的方法。然而,反模式只将部分集合加载到Vendor.Promotions集合,这不是很好吗?事实上,对于供应商实体的消费者来说,集合不完整是隐式的(隐藏的),IMO的最佳实践是将事情明确化。域模型的目的是确保满足您的不变量,所以我不认为它是任何形式的反模式。另一个例子可能是
ActiveOrders
Customer
的操作,在这种情况下,用户肯定不想要或永远不需要整个历史订单集合。有时我们可以应用一些技术“技巧”来满足不变量的要求。经常对集合或计数进行筛选可能会有所帮助。随着时间的推移,我们可能会找到更好/更好的方法来完成同样的事情,但我们必须务实对待这些事情。希望有帮助:)好的,我不认为过滤收集是可以接受的。无论是在文章、教程还是播客中,都从未见过这种方法。感谢您的建议:)所以这里的关键概念是,在AddPromotion期间,我可以清除过期的促销,并且促销历史记录(我确实需要)应该在read模型的另一个表中。然而,读模型只是真理之源写模型的投影,所以IMO历史也应该是写模型的一部分。因此,如果另一个表引用升级(活动或历史),则不可能在DB中具有适当的关系(不能将FK与两个不同的表关联)。我在这里看到的解决方案是打破“每个事务更新一个聚合”规则,在AddPromotion处理程序中。。。。。。我将用活动促销列表更新两个“供应商”聚合,并创建类型为“促销历史项目”(也称为AR)的新实体。也许我不应该称它为read model,而应该称它为促销历史。此表可以是促销历史记录的真实来源,供应商是允许创建促销的机构。此外,对于现在的模型,升级是一个值对象,因此不能根据定义引用它,即使它是一个具有Id的实体,也不应该直接使用FK,因为这违反了只引用聚合根的规则。我不会担心的
public Vendor : Aggregate {
    public Guid Id;
    public List<Promotion> Promotions;
    // some other Vendor props here

    public void AddPromotion(Promotion promo) {
        // protect invariants (business rules) here:
        // rule_1: if 2 promotions are already active during any time between promo.Start and promo.End then throw ex
        // rule_2: if during any time between promo.Start and promo.End there is promo with same Type then throw ex

        // if all is ok (invariants protected) then:
        Promotions.Add(promo);
    }
}

public Promotion : ValueObject {
    public PromotionType Type; // enum CheapestItemForFree, FreeDelivery, Off10PercentOfTotalBill
    public DateTime Start;
    public DateTime End;
}
public class PromotionsDomainService {
    public Promotion CreateNewVendorPromotion(Guid vendorId, DateTime start, DateTime end, PromotionType type) {
        // protect invariants here:
        // invariants broken -> throw ex
        // invariants valid -> return new Promotion aggregate object
    }
}