Joh*_*Bzz 5 domain-driven-design aggregate invariants
我知道聚合应该很小并且它们应该保护不变量。我也知道在聚合中保存大型集合会影响性能。
我有一个用例,需要保护它的不变量,但也会导致大量收集。
Aggregate 是Vendor,它可以有多个活动的Promotion (s)。每个Promotion都有PromotionType、StartDate和EndDate。不变量是:
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;
}
Run Code Online (Sandbox Code Playgroud)
正如我们所见,Promotions随着时间的推移,新的促销活动不断增加,收藏会增加,而旧的促销活动将会过期。
解决方案 1) 一种可能性是自己制作Promotion一个包含VendorId的聚合,但在这种情况下,很难保护提到的不变量。
解决方案 2)另一种可能性是有一个维护工作,将过期(结束日期传递)移动到某个历史表,但它是 IMO 的臭解决方案。
解决方案 3)另一种可能性是自己也进行Promotion聚合,但保护域服务中的不变量,例如:
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
}
}
Run Code Online (Sandbox Code Playgroud)
...但是在PromotionsDomainService(并返回 Aggregates)中保护它,我们冒着竞争条件和不一致的风险(除非我们应用悲观锁)。
在这种情况下,推荐的 DDD 方法是什么?
您的聚合应仅包含实现其目的所需的数据。阅读您的问题描述,我认为供应商不需要任何过期的促销活动。因此,您只需保留集合中的活动促销即可。
在 AddPromotion 方法中,如果存在该类型的活动促销,您将返回错误。如果没有该类型的任何促销,您将添加它;如果该类型的促销已过期,您将替换它。除非您有大量促销类型(情况似乎并非如此),否则每种类型最多只能有一次促销。看来这将使收藏保持在一个非常合理的规模。如果情况并非如此,请告诉我。
您很可能需要过期的促销活动作为历史数据。但这些应该是在为此目的而设计的读取模型上,而不是在聚合中。为此,聚合可以针对其接受新促销的每种类型发布一个事件,并且侦听器将对该事件做出反应并在历史促销表中插入一条记录。
更新:
再次阅读该问题后,我意识到您甚至不需要为每种类型保留一个促销。集合中最多有 2 个促销活动,因此集合的大小最多为 2,除非我误解了。