Event sourcing - where is the domain logic located?

I just watched Greg Young talk about Event Sourcing, but I'm confused where business logic fits in. A simple example:

1) Shopping Cart Created 2) Item Added 3) Item Added 4) Promotional Code - 20% Off 

An ad code was calculated for the items in the shopping cart and the result of storing it as an event. I understand that "PromotionalCodeAddedEvent" may make sense, but where does the math happen? I think:

 public void AddPromotionalCode(PromotionalCode code) { //perform calculation against shopping cart items. //if valid ApplyChanges(cmd); } 

Then the result does not end anywhere, and the Read Model will have to perform the calculations.

I do not quite understand the concept, any help would be wonderful.

+6
source share
4 answers

Something as simple as a promo code can be a pretty complicated use case. First of all, this is due to the fact that the promotional code is a logic that (as a rule) is supported by one or several business users, while it also belongs within the domain. This is unconventional, in that sense. There are several ways to deal with this, and what I describe will be just my personal approach.

For argument, suppose you have a simple series of well-known promotional codes, for example:

  • X% Off Purchase with or without a minimum purchase
  • $ X Off Purchase with or without a minimum purchase

We can make some assumptions:

  • Ad code has a start date.
  • Ad code has an end date.

Using a promotional code can be difficult. Consider the two scenarios that we have identified. "$ X Off Purchase" is relatively simple, as a fixed amount. However, buying an "X% Off Purchase" is more complicated. If we only had a fixed amount, we could apply the discount to the trolley as soon as any threshold values ​​are reached. If there is a discount on the interest rate, if the user has to add two elements, add a promotional code, and then add another element, the promotion will already be "applied".

Because of this, I personally “attached” the ad code to the cart. What for? The reason is that at the time of verification, I can assume that the trolley will be used to create the order. Until this time, the contents of the basket are liquid. The user’s actions against the basket will change the total value of the basket, as well as the total discount if there is an unfixed discount amount. It can also invalidate the discount if the user removes one or more items from the basket and the total price of the basket drops below the threshold to apply the discount.

So, I would have a few teams that would be involved. In fact, any team that affects the value of the basket can change the discount amount. To this end, I will look for the amount of discounts that will be recounted for teams that:

  • Add product to cart
  • Remove item from cart.
  • Change the number of items in the cart
  • Add promotional code to cart
  • Change the promotional code attached to the basket.

Since all these operations are against the basket, I would have calculated the discount as part of the promotional code, myself with the participation of the data contained in the basket. It seems that the promotional code will constitute the aggregate, going along this path. Thus, I would call command handlers a domain service that can provide my cart with the necessary information. This domain service will download the promotional code, and I'm going to go through the positions of this basket so that the promotional code will tell me what the calculated discount will be. Then I am going to create an event that contains the new basket value, as well as the adjusted value (discount). Following this path, the logic for calculating discounts based on the positions in the basket is responsible for the promotional code.

Instead, you can put this responsibility in the basket. Personally, however, it seems to me that encapsulating domain logic in a promotional code, in itself, makes more sense. I mentioned that it is likely that you will create an order from the basket. Having a promotional code as an aggregate and containing the logic of applying discounts based on positions, we have the only truth in how we calculate the discount on positions - whether from the point of view of a cart or as members of an order.

+3
source

You can, for example, raise a second event, such as PromotionalCodeApplied , which contains the results of the calculations.

The read model then simply needs to use the pre-computed result.

+1
source

I understand that "PromotionalCodeAddedEvent" may make sense, but where does the math happen?

This should happen in teams that make changes to the cart. Each such command will call some method, for example RecalculateTotals (), where all business logic will be placed.

Consider the following pseudo code:

 public void AddPromotionalCode(PromotionalCode code) { var @event = new PromotionalCodeAdded(code); var amount = RecalculateTotalAmount(extraEvent: @event); @event.TotalAmount = amount; _eventStore.Publish(@event); } decimal RecalculateTotalAmount(IEvent extraEvent) { var relatedEventTypes = new[] { typeof(PromotionalCodeAdded), typeof(ShoppingCartCreated), typeof(ItemAdded) }; var events = _eventStore.ReadEventsOfTypes(relatedEventTypes); var events = events.Concat(new[] { extraEvent }); //calculation logic goes here based on all related events } 
+1
source

Here I like to return events from command methods. As Alexander Langer mentioned, you apply “math” and return the corresponding event (s):

 public PromotionalCodeApplied AddPromotionalCode(PromotionalCode code) { //perform calculation against shopping cart items. var promotionalCodeApplied = new PromotionalCodeApplied(code.VoucherNumber, discountAmount, DateTime.Now); On(promotionalCodeApplied); return promotionalCodeApplied; } public void On(PromotionalCodeApplied promotionalCodeApplied) { _voucherNumber = promotionalCodeApplied.VoucherNumber; _discountAmount = promotionalCodeApplied.DiscountAmount; _discountDate = promotionalCodeApplied.DateAppllied; } 

Your reading model now has access to the appropriate values.

0
source

Source: https://habr.com/ru/post/1014011/


All Articles