patterntypescriptModerate
State Machine Pattern: Model Entities With Explicit Allowed Transitions
Viewed 0 times
state machinefinite state machinetransitionsorder statuslifecycleinvalid statexstate
Problem
An entity (order, connection, UI component) has different behaviors depending on its current state and invalid transitions cause data corruption. Boolean flags and if/else chains become unmanageable.
Solution
Define all states as a union type. Map valid transitions explicitly. Throwing on invalid transitions prevents impossible states.
type OrderStatus = 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';
const transitions: Record<OrderStatus, OrderStatus[]> = {
pending: ['confirmed', 'cancelled'],
confirmed: ['shipped', 'cancelled'],
shipped: ['delivered'],
delivered: [],
cancelled: [],
};
class Order {
constructor(public status: OrderStatus = 'pending') {}
transition(next: OrderStatus): void {
if (!transitions[this.status].includes(next)) {
throw new Error(`Invalid transition: ${this.status} -> ${next}`);
}
this.status = next;
}
}
const order = new Order();
order.transition('confirmed'); // ok
order.transition('delivered'); // throws: confirmed -> delivered is invalidWhy
Explicit state machines make the business rules visible and enforceable in code. Invalid state transitions throw at the point they are attempted, not silently corrupting downstream logic.
Gotchas
- State machines are for entities with lifecycle rules. Don't overuse them for simple boolean flags.
- For complex machines with entry/exit actions, consider a library like XState rather than rolling your own.
- Persist the state string, not the object reference, to databases or message queues.
Revisions (0)
No revisions yet.