Entity-Backed FSM
1. Context
Section titled “1. Context”A persistent entity — an order, an invoice, a shipment — has a well-defined lifecycle: a limited set of valid states and transitions, and every transition must update the stored record.
2. Problem
Section titled “2. Problem”How do you centralize the permitted state transitions for a persistent entity
without scattering status-check if logic across every service method that
might change it?
3. Forces
Section titled “3. Forces”- Consistency — the entity’s persisted status and its in-memory transition must never disagree.
- Coupling — the FSM needs to know which persisted field it controls, without owning the entity’s other persistence concerns (queries, indexes).
- Reuse — the same entity may be read through several unrelated database operations that must not bypass the FSM’s transition rules.
- Complexity — entry/exit rules (timestamps, computed fields) need one defined place to live, not one per caller.
4. Solution
Section titled “4. Solution”Declare an @fsm that controls one persisted field of a @database
entity (controls Order.status), keyed by the entity’s identity. Declare
each valid event, and per-state on event rules using
next <State> : <field assignment>. Treat the FSM as the ownership boundary
for the controlled field: API and service operations should trigger FSM events
instead of exposing database commands that modify that field directly. Other
database queries and commands remain free to read and write the rest of the
entity.
5. Structure
Section titled “5. Structure”stateDiagram-v2 [*] --> New New --> Approved: approve New --> Cancelled: cancel Approved --> Shipped: ship Approved --> Cancelled: cancel Shipped --> Delivered: deliver Delivered --> [*] Cancelled --> [*]6. DSL Sketch
Section titled “6. DSL Sketch”@database
Database OrderDb engine = postgres configType = DatabaseConfig
Entity Order key(id) query findById(id: String) : Order command T updateOrder(order: Order) : Order
@fsm
OrderFSM controls Order.status key: id
event approve in(_) out(order:Order) event ship in(_) out(order:Order) event cancel in(_) out(order:Order)
state New on event approve: next Approved : this.updatedAt = nowUTC() on event cancel: next Cancelled : this.updatedAt = nowUTC()
state Approved on event ship: next Shipped : this.updatedAt = nowUTC()
state Shipped state Delivered state CancelledThis is a minimal illustrative fragment, not a complete duplicated example — see Working Examples for the full, runnable model.
7. Consequences
Section titled “7. Consequences”Benefits
Section titled “Benefits”- Transition rules live in one place, independent of every API or service that might trigger them.
- Unsupported transitions (for example,
Deliveredback toShipped) are absent from the FSM’s event rules rather than guarded by scattered conditionals. - Per-transition field updates are explicit and auditable.
Trade-offs
Section titled “Trade-offs”- The FSM only controls the one field it declares; broader entity consistency (other fields, related entities) remains the service’s and database’s responsibility.
- The model must avoid alternate database commands that bypass the FSM and modify the controlled field directly unless the active toolchain explicitly enforces exclusive ownership.
- Adding a new state or event means touching the FSM definition — a single coupling point, by design.
8. Alternatives
Section titled “8. Alternatives”- If the behavior has no persisted identity worth controlling (a transient calculation session), use In-Memory FSM instead.
- If the transitions are really human approve/reject decisions with a recorded actor, see Approval Workflow, which specializes this pattern.
9. Related DSL
Section titled “9. Related DSL”- FSM DSL Reference —
controls, events, states, transitions. - Database DSL Reference — the entity and field the FSM controls.
- Service DSL Reference — connecting API operations to FSM events.
10. Working Examples
Section titled “10. Working Examples”- Entity-Based Order FSM — an order lifecycle governed by an FSM controlling a persisted status field.
- Invoice Approval Workflow — a decision-oriented variant; see Approval Workflow.
11. Related Patterns
Section titled “11. Related Patterns”- In-Memory FSM — the alternative when there is no persisted entity to control.
- Approval Workflow — a specialization for human decision-driven transitions.
- Database-Backed CRUD — the free-form persistence and API surface this pattern constrains for one field.