0

My aggregates are:

  1. Customer who has multiple Discounts which are invalidated if used during ordering
  2. Order which has to be modified if customer who places it has specific discount

My problem is that when an Order is placed (new Order object gets created) I have to both persist new Order object and deactivate appropriate Discount. This has to be done in one transaction since it is user request. This violates the rule of DDD "one aggregate modification per transaction".

Is it ok to do this in one transaction since new object being persisted can be considered not a change or should I somehow remodel this? Just to mention, they reside in same Bounded Context.

Mike
  • 812
  • 9
  • 25

2 Answers2

2

It depends :)

Transactions put a limit on the number of concurrent operations your system can handle. Is that limit a problem or not, use cases and db implementation details are needed to check.

On the other hand, transactions make things much easier.

Reading the comment on another response I saw:

eventual consistency cannot be used because then Customer would be able to use one discount for multiple orders

On a distrubuted system (modelled using DDD) the only way to guarantee this, is having the Discount and the Order under the same aggregate, because the aggregate define the consistency boundary, you can check invariants on the same data that will be stored, atomically.

Using a transaction, you are (in some way) expanding the boundary of your aggregate to have the Order and Discount in it, as no concurrent operation can be executed on the two entities (because of the transaction locks).

Opening to eventual consistency usually is done having the inconsistencies managed as businness domain rules. One way to do it could having the rules for when a Discount is used two times.
This can be done in the process manager handling the event that when it tries to "Deactivate" the Discount it rejects the command because "AlreadyDisabled". The ProcessManager knowing the possibility of a rejection because AlreadyDisabled at that point can cancel the Order, or change it in some way, notifying some system or whatever is the best strategy (from the business perspective). But in that case the "process" of the order creation takes into account the fact that it can happen that a discount is used for the second time.

Obviously the implementation of techincal implementation of events dispatching should minimize the possibility of that happening, but still it will be possible (we are talking about handling 100% of the cases)

Transactions make handling these cases easier, but put a limit on the reachable scale of the system. Solutions that allows for big scale of the system, needs to manage lot of details and require bigger effort to be implemented.

As last thing, domain events could be modelled and used in a way for which when an aggregate is stored, events get published and you have a single transaction spanning the aggregate change and all the operation done by the events listeners (process managers).
The good thing of this is that in this way you are decoupling the Order and Discount, without having the part of the systems managing them having to know each other, and/or could be simpler to add other processing, plus you can test processes in isolation (you can manually publish an event to a process manager without the need of having to have to do with the Order).

What's the best solution? It's a matter of trade-off on your use case

rascio
  • 8,968
  • 19
  • 68
  • 108
  • Thanks for the answer. It seems I don't have the problem with locking since new Order is created when appropriate Discount is "used"/deactivated which is to say the Order is not locked. The question is actually: is it ok to create & persist new aggregate from another aggregate? This method that deactivates Discount and creates new aggregate is actually a factory. – Mike Feb 26 '23 at 13:33
  • 1
    you can perfectly have factory method, one aggregate can create another, and you wrap the interaction into a domain service, that is scoped into a single transaction – Sylvain Lecoy Feb 26 '23 at 13:59
  • I think the major issue of two aggregates knowing each other is coupling. In the sense that if later on you need to completely detach them the refactor could be harder. That's why events are used, in that way the Order aggregate know its own event, the Discount know its own command, and a Process manager is used to encapsulate the knowledge/dependency between the two aggregates. – rascio Feb 26 '23 at 14:36
  • In your case the factory could be acting a little bit as a Process manager – rascio Feb 26 '23 at 14:37
  • 1
    BTW I re-read the response and found some part very confusing, I tried to rewrite them better :P – rascio Feb 26 '23 at 14:51
1

In your specific case I would model the Discount as value object within the Order aggregate. Make much more sense and resolve your rule violation.

If you want to keep the Discount modeled as a part of the Customer aggregate then you can dispatch an event from the Order, listen to that event and eventually update the user.

But in your case I would go for the first solution.

Sylvain Lecoy
  • 947
  • 6
  • 15
  • First proposed solution doesn't quite work since Customer can have multiple Discounts and multiple Orders which means Discount has to be modeled as part of Customer. When new Discount is registered there doesn't have to exist an order. And the problem with second solution is that eventual consistency cannot be used because then Customer would be able to use one discount for multiple orders, until the event gets handled and Discount is deactivated. – Mike Feb 26 '23 at 12:09
  • 1
    ok then your case is to use a domain service that will make both aggregate consitent in the same transaction. – Sylvain Lecoy Feb 26 '23 at 13:54
  • Thanks for the answer. It seems like a fine solution. So, "you should not modify 2 aggregates in one transaction" - from the blue book by Vernon has to be violated sometimes, or have I misunderstood it? Domain service can modify many aggregates in one transaction? – Mike Feb 26 '23 at 15:46
  • 1
    i've read more than 10 times the red book by Vernon, when you need to modify multiple aggregate in one transaction, either you re-design your domain model, either you write domain services. I have examples where some of my aggregates does create (factory methods) other aggregates and save same in a transaction. I have since then learnt to better model the domain so I don't have this anymore (aggregate are now composed of value objects referencing by id others aggregates that are not part of the transaction). But as a temporary solution its fine – Sylvain Lecoy Feb 26 '23 at 20:01
  • For instance if the discount coupon are related to user, they apply to a cart, your order list. This could be modeled as an entity (having an identity, and a lifespan) in the Customer aggregate, and derived as value object in your cart. User would be requested by a domain service, and factories methods on user would create the value objects needed in your cart. – Sylvain Lecoy Feb 26 '23 at 22:25