State Machine for Inventory Movements
The 3-Line Engineering Rule
Model multi-step business entities (inventory, orders, payments) as strict Finite State Machines with explicit transition tables.
Free-form status columns updated with arbitrary strings lead to impossible states (e.g. shipping an item that was cancelled or refunding a returned item twice).
Enforce transition guards, immutable state history logs, and reject illegal state transitions at both domain code and database constraints.
Architectural Blueprint & Sequence
Hover over nodes to inspect state transitionsAVAILABLE
In warehouse stock
RESERVED
Checkout started (TTL: 15m)
ALLOCATED
Payment succeeded
DISPATCHED
Handed to courier (TERMINAL)
RELEASED
Cart expired / cancelled
Key Engineering Takeaways
- Declare explicit valid transitions (e.g., `RESERVED -> ALLOCATED -> DISPATCHED`).
- Disallow transitions out of terminal states (`CANCELLED`, `COMPLETED`, `REFUNDED`).
- Record every transition event with actor, timestamp, previous state, and reason in an audit table.
- Use database enum types or check constraints to reject unauthorized status values.
The Problem & Production Reality
Physical inventory movements (e.g. e-commerce warehouses, fulfillment centers) cannot be rolled back with an undo button. Once an item is dispatched into a courier van, changing its database row to `CANCELLED` creates an operational disaster. Modeling lifecycles with state machines prevents illegal operations.
Anti-Pattern: Unchecked String Status Mutations
Allowing controllers to set `order.setStatus("CANCELLED")` regardless of whether the warehouse already printed shipping labels.
// ❌ ANTI-PATTERN: Direct status overwrite without validation
public void cancelOrder(String orderId) {
Order order = orderRepo.findById(orderId);
// If order was already SHIPPED, setting CANCELLED creates ghost inventory!
order.setStatus("CANCELLED");
orderRepo.save(order);
}Production Pattern: Strongly-Typed Enum State Machine with Guards
Encapsulate states and their permissible transitions directly in an enum or dedicated state machine library.
// ✅ PRODUCTION PATTERN: Domain State Machine with Guards
public enum InventoryState {
AVAILABLE {
@Override
public InventoryState transition(InventoryEvent event) {
if (event == InventoryEvent.RESERVE) return RESERVED;
throw new IllegalStateTransitionException(this, event);
}
},
RESERVED {
@Override
public InventoryState transition(InventoryEvent event) {
if (event == InventoryEvent.PAYMENT_CONFIRMED) return ALLOCATED;
if (event == InventoryEvent.TIMEOUT || event == InventoryEvent.CANCEL) return AVAILABLE;
throw new IllegalStateTransitionException(this, event);
}
},
ALLOCATED {
@Override
public InventoryState transition(InventoryEvent event) {
if (event == InventoryEvent.DISPATCH) return DISPATCHED;
if (event == InventoryEvent.REFUND_APPROVED) return AVAILABLE;
throw new IllegalStateTransitionException(this, event);
}
},
DISPATCHED {
@Override
public InventoryState transition(InventoryEvent event) {
// TERMINAL STATE: Cannot transition backwards!
throw new IllegalStateTransitionException("Cannot cancel an order already dispatched!");
}
};
public abstract InventoryState transition(InventoryEvent event);
}Trade-off Analysis & Staff Recommendations
| Aspect | Pros / Strengths | Cons / Hazards | Staff Recommendation |
|---|---|---|---|
| In-Memory Enum vs Spring StateMachine Library | Enum-based state machines are fast, zero-dependency, and compile-time verified. | Spring StateMachine supports complex hierarchical nested states and persistence out of the box. | Use lightweight enum state machines for < 10 states; adopt Spring StateMachine for complex workflows with dozens of sub-states. |
| Audit Trail Storage | Full auditability of every state change for support and financial compliance. | Requires writing to an append-only audit table on every transition. | Always write state transition audit rows within the same local database transaction. |