I understand that Aggregates should be small and they should protect invariants. I also know that keeping large collections in Aggregates impacts performance.
I have a usecase, that needs to protect its invariants, but also will lead to large collection.
Aggregate is Vendor, and it can have multiple active Promotion(s). Each Promotion has PromotionType, StartDate and EndDate. The invariants are:
- at any point of time there can be max one promotion of each PromotionType
- at any point of time there can be max 2 promotions
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;
}
As we can see, Promotions
collection will grow while new promotions are added during time, and old promotions will get expired.
solution 1)
One possibility is to make Promotion
an aggregate on its own, containing VendorId, but in that case it would be difficult to protect mentioned invariants.
solution 2) Another possibility is to have a maintenance job that will move expired (EndDate passed) to some history table, but it's smelly solution IMO.
solution 3)
Yet another possibility is to also make Promotion
an aggregate on its own but protect the invariants in Domain Service, e.g.:
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
}
}
... but protecting it in PromotionsDomainService (and returning Aggregates) we risk race condition and inconsistency (unless we apply pessimistic lock).
What is recommended DDD approach in such case?