0

Let's take an example of Order.

An order can have the state new, canceled, shipped, or processing.

  • The new order can be processed.
  • The new order can be canceled.
  • The processing order can be shipped.
  • The processing order can be canceled.
  • The canceled order cannot be processed, shipped, or new.
  • The shipped order cannot be canceled, processed, or new.

I have 2 approaches to solve this problem.


1. Throws error if the state change is invalid

class Order {
  private state: string;

  ship() {
    if (this.state !== "processing") {
      throw new Error("Cannot ship an order that is not processing");
    }
    this.state = "shipped";
  }
}

This approach does the job. It blocks invalid state changes and throws error to fail fast.

I don't like this approach. It throws a lot of errors and does not tell the client how to use the domain object correctly. The clients can call the ship method whenever they want and they have to read the implementations to know how to use the domain object correctly.

Then I have the second approach.

2. Every state should have its class

class ProcessingOrder {
  readonly state = "processing";

  ship(): ShippedOrder {
    return new ShippedOrder();
  }
}

class ShippedOrder {
  readonly state = "shipped";
}

This approach blocks the invalid state changes by providing only the valid behavior. It teaches the client how to work with the object and minimize possible errors.

The thing I don't like about this approach is the method has to return another object. This violates the concept of Entity in Domain-Driven Design. The same entity is created multiple times because of the state change.


Which approach should I use?

If you don't use any of my approaches, could you tell me your approach?

9
  • Just curious. Does DDD allow entities belonging to an aggregate to be changed out of the aggregate? Do they even have logic like the one involving a machine state? If not, the argument "clients can call/change the state any time anywhere is no longer valid. On the other hand wouldn't "Shipping" be a use case /op operating at much higher level of abstraction than the one entities live in?
    – Laiv
    Commented Aug 17, 2023 at 11:27
  • 1
    Does this answer your question? How to implement state machine pattern on aggregate root Commented Aug 17, 2023 at 12:17
  • Why does it "violate the concept of Entity in Domain-Driven Design"? Commented Aug 17, 2023 at 12:18
  • 1
    My experience: there isn't a good approach here, just lots of disappointing ones with different tradeoffs. Commented Aug 17, 2023 at 16:26
  • 1
    I've never heard of "you want to keep the entity object as long as possible" from an OO perspective. It's even a typical thing to implement custom comparison to determine if one entity object is the same as another... Commented Aug 18, 2023 at 7:51

0

Your Answer

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