0

In the book Patterns, Principles, and Practices of Domain-Driven Design, chapter 16, entities, Avoid the State Pattern; Use Explicit Modeling has brought the great idea of domain modeling to my life.

It's easy to implement this idea with Entity. But with Aggregate Root, I find it's hard to implement.

In DDD, Aggregate Root is a special Entity. There can only be one Aggregate Root in an Aggregate. And that's what causing the trouble.

For example, I have the Order, which has states like Placed, Confirm, Shipped.

If we use explicit modeling, we will have PlacedOrder, ConfirmOrder, ShippedOrder.

Are all of these Aggregate Roots?

How to implement this idea correctly with Aggregate Root?

8
  • What exactly is the problem? Let's say they are Aggregate Roots. Does that make any difference? Can you elaborate a bit on what exactly does or doesn't work? Commented Dec 3, 2021 at 11:15
  • @RobertBräutigam In one aggregate, there can only be one aggregate root, so that's the problem. If they are all Aggregate Roots, which one would communicate with other Aggregate Root? Commented Dec 3, 2021 at 11:18
  • I imagine there would be a placedOrder.confirm(...) which returns a ConfirmedOrder, or some such. That would be perhaps the extent of "communication". Otherwise I don't really know what you mean. Commented Dec 3, 2021 at 11:22
  • @RobertBräutigam Oh, I see the problem here. I am thinking that the Order is the Aggregate Root, and other classes are just its states. You are thinking that they are all Aggregate Roots in different Aggregates. Ok, so following your thought, if the PlacedOrder and ConfirmedOrder are both Aggregate Roots in separate Aggregates, and the placedOrder.confirm(...) returns a ConfirmedOrder, would it violate the Aggregate Root must reference other Aggregate Root by its ID principle? Commented Dec 3, 2021 at 11:27
  • PlacedOrder doesn't necessarily has to reference ConfirmedOrder at all. It may just create it. Just to be clear, I don't agree with that "ID" rule at all in the first place. We're not a database, we don't work with primary keys and foreign keys. I know it's in the Blue Book, but even Evans has distanced himself a bit from these concrete things a bit over the years. So I don't think it is a "must" for DDD. YMMV Commented Dec 3, 2021 at 11:40

2 Answers 2

3

Instead of modeling the state alone, you could make PlacedOrder, ConfirmedOrder and ShippedOrder an "aggregate root", so there is no real aggregate over them.

Those could create the next one as state transitions. I.e. ConfirmedOrder placedOrder.confirm(...).

This is not exactly the state pattern you described, but one potential modeling option nonetheless.

2

I think Robert Bräutigam's answer is entirely feasable, but, with apologies, I'm going to argue that in context it's inappropriate. Sorry!

Having a 'state' in an entity does not make it a state machine. True, in this application a Placed order becomes a Confirmed one, and a Confirmed order becomes a Shipped one, and no other transitions are allowed[1]; therefore a very, very minimal bit of State-Machine-like code needs to be written. However, that's no different to having each Aggregate Root enforcing that their Confirm(..) etc methods only take the relevant types as parameters; that's just as much a State Machine but in disguise. It also gives you a lot of extra trouble: you now have to write three Roots instead of one, and maintain them for hopefully a long time. Also, how do you get hold of the customer's order history? You either have to collect and merge from 3 Roots, or add a fourth.

I would think it much better to have one Order root, with Confirm, Ship, etc methods which check that Order to be X'ed is in a proper state to be X'ed.

After all, we do call any 'atomic' data item in a class part of the State of the class, and sometimes have rules about when they can change.

State Machine for me is for far more complicated situations, where there are many possible states and complex rules about how they transition between themselves. Things like a Parser for some 'language', for instance (I have done one for fairly simple arithmetic). A simple 3-valued field calls for an enum, and a bit of validation around how it gets changed cannot to my mind be called a State Machine without having left my glasses at home.

[1] There may be more than the 2 transitions identified - reverse ones, e.g., but my point that this is a very simple situation stands I think.

4
  • 1
    In any domain, multiple models will be able to satisfy the business rules. Each model has different pros and cons and we don’t know nearly enough about the specific business rules for this question to argue that one model is better than another. So while it’s possible that your solution is better, it does not answer the question. Fwiw, I find Robert’s solution more elegant in that it enables us to place different behavior in those aggregates; I don’t want a confirm method in an order that’s already shipped.
    – Rik D
    Commented Dec 3, 2021 at 15:19
  • 1
    @RikD has explained why Robert's solution answers my question. Moreover, making them all Aggregate Roots does not mean we have to merge 3 roots, because the Order history has different concerns, and could even be in a different bounded context. Commented Dec 4, 2021 at 1:49
  • 1
    @RikD Maybe Robert's solution is more elegant, but more likely, it is over-engineering. The aggregates that Robert describes are likely nowhere near as distinct as it seems. How about ChangeShippingAddress? That probably goes on 2/3 of them right? How many more methods will we need to duplicate? Furthermore, it is exceedingly likely (and given that this post is 2 weeks old the OP has probably already encountered this) that we don't care what "state" the Order is in or only are about the "state". The abstraction is poor in both scenarios. Commented Dec 14, 2021 at 15:32
  • 1
    @RikD And you do want a Confirm method on all orders (i.e. an interface) if we are actually implementing a state machine. Otherwise you have to either disambiguate the types or deal with all sorts of added complexity to simply retrieve an order (i.e. You either need 1 query that can return any order type -- which sucks! Or you need 3 queries that return only 1 order type, but now you get to deal with figuring out which one to invoke). Neither of these options are fun or... elegant. Commented Dec 14, 2021 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.