.netentity-frameworkmicroservicesdomain-driven-designaggregateroot

Modeling a many to many relationship to a DDD aggregate


Trying to understand how this can be modeled without breaking DDD rules.

This is a system for managing and sending Gift Cards to Customers

Customer
-------
CustomerId (Primary Key)
CustomerType
FirstName
LastName

GiftCard
------
GiftcardId (Primary Key)
ShopName
Suspended
CreatedDate

CustomerGift
-------------
CustomerId (Foreign Key -> Customer)
GiftcardId (Foreign Key -> Giftcard)
GiftDate

I have the following models

public class Customer
{
  Int CustomerID;
  String FirstName;
  String LastName;
  String CustomerType; 
  IEnumerable<CustomerGift> CustomerGifts;
}

public class CustomerGift
{
    Int GiftCardID;
    Int CustomerId;
    DateTime GiftDate;
}

public class GiftCard
{
    Int GiftCardID;
    String ShopName;
    Bool Suspended
    DateTime CreatedDate;
}

Domain rules:

To my understanding the Customer and GiftCards are aggrigate roots and the CustomerGift is a child.

However at its current state there is no way to enforce the "GiftCard can be sent upto 50 customers" rule without exposing the CustomerGift to the GiftCard. Which would break the rule by sharing a child entity.


Solution

  • In general, when modeling a domain, it's best to forget about what the tables in a relational schema would look like.

    Your Gift Card aggregate can track which customer holds a given gift card (assuming that a given gift card can only be held by at most one customer at a time; if it's at least zero customers at a given time, then this becomes a set). Your Customer aggregate can track how many and which gift cards a given customer holds (as it does).

    This suggests that the process of assigning a gift card to a customer progresses like:

    You'd then have (whether or not you had the process cancel a reservation if association failed) a periodic reconciliation: check for reservations of some age and if they're associated, complete them and otherwise cancel them.

    As you may note, this isn't atomic. The real world isn't atomic either and we're capturing reality.

    At least one of suspension or spending a gift card is probably going to be a process like the assignment process. It's probably advisable to make the more exceptional use case a multi-step process: if you expect more gift cards to be spent than suspended, then you can make suspension a process like

    and then spending the gift card can be done just through the customer aggregate (double-spend prevented by the customer) with a later reconciliation process updating the gift card as spent. Alternatively, you can make suspension an operation that only touches the gift card aggregate and spending requires a pas de deux (mark customer's wallet with intend-to-spend, spend gift card, mark as spent).