Something as seemingly simple as a promotional code can actually be a fairly complex use case. Primarily, this is due to the fact that a promotional code is logic that (usually) is maintained by one or more business users, whereas it also belongs inside of the domain, itself. It is non-traditional, in that sense. There are multiple ways to handle this, and what I will outline would just be my personal approach.
For the sake of argument, assume that you have a simple series of known promotional codes, such as:
- X% Off Purchase, with or without a Minimum Purchase
- $X Off Purchase, with or without a Minimum Purchase
We can make some assumptions, as well:
- A Promotional Code has a Start Date
- A Promotional Code has an End Date
The application of a promotional code can be tricky. Consider the two scenarios that we have defined. "$X Off Purchase" is comparatively simple, because it is a fixed amount. "X% Off Purchase", however, is more complex. If we only had the fixed amount, we could apply the discount to the cart as soon as any thresholds are met. With a percentage-based discount, if the user were to add two items, add a promotional code, and then add another item, the promotion would have already been "applied".
Because of this, I would personally only "attach" a promotional code to a cart. Why? The reason is that at the point of checkout, I can likely assume that a cart is going to be used to generate an order. Until that time, the cart's contents are fluid. A user's action against the cart will change the total value of the cart, as well as the total discount, assuming a non-fixed discount amount. It can also render either discount as being invalid, should a user remove one or more items from a cart and the total value of the cart fall below the threshold to apply the discount.
So, I would actually have multiple commands which would would be involved. Essentially any commands that impact the value of the cart could change the discount amount. To that end, I would be looking for the discount amount to be recalculated for the commands which:
- Add an item to the cart
- Remove an item from the cart
- Change the quantity of items in the cart
- Add a promotional code to the cart
- Change the promotional code attached to the cart
Since these all are operations against a cart, I would be calculating the discount within the promotional code, itself, with the participation of the data contained in the cart. It feels like a promotional code is going to be an aggregate, going down this path. So, I would have the command handlers invoke a domain service that can provide my cart with the information that it requires. That domain service is going to load the promotional code, and I am going to be able to pass in the line items within that cart, so that the promotional code will tell me what the calculated discount would be. Then, I am going to generate the event, which contains the new value of the cart, along with the adjusted value (the discount). Going down this path, the logic for calculating a discount based upon line items within a cart is the responsibility of the promotional code.
You could put this responsibility in the cart, instead. Personally, though, I feel as if encapsulation of domain logic within the promotional code, itself, makes more sense. I had mentioned that it is likely that you will generate an order from a cart. By having the promotional code as an aggregate, and it containing the logic to apply the discount based upon the line items, we have a single truth, in how we calculate a discount for line items - whether that is in terms of a cart or in terms of an order.