State Machine Design Skill
When to Use This Skill
Use this skill when:
-
State Machine Design tasks - Working on statechart and state machine modeling for lifecycle and behavior specification
-
Planning or design - Need guidance on State Machine Design approaches
-
Best practices - Want to follow established patterns and standards
Overview
Design finite state machines and statecharts for modeling entity lifecycles, workflows, and system behavior.
MANDATORY: Documentation-First Approach
Before designing state machines:
-
Invoke docs-management skill for state machine patterns
-
Verify implementation patterns via MCP servers (context7 for XState, etc.)
-
Base all guidance on Harel statechart semantics
State Machine Concepts
Core Elements
Element Description Example
State Condition the system can be in Draft , Submitted , Paid
Transition Change from one state to another Draft → Submitted
Event Trigger for a transition Submit , Pay , Cancel
Guard Condition that must be true [hasItems] , [isValid]
Action Side effect on transition sendNotification , updateDatabase
Entry Action Action when entering state onEnter: startTimer
Exit Action Action when leaving state onExit: stopTimer
State Types
public enum StateType { Initial, // Starting state (filled circle) Normal, // Regular state Final, // End state (circle with border) Composite, // Contains sub-states Parallel, // Concurrent regions History, // Remember last sub-state Choice // Decision point }
State Machine Notation
PlantUML Syntax
@startuml title Order State Machine
[*] --> Draft : Create
state Draft { Draft : entry / initializeOrder Draft : exit / validateOrder }
Draft --> Submitted : Submit [hasItems] Draft --> Cancelled : Cancel
state Submitted { Submitted : entry / reserveInventory }
Submitted --> Paid : ProcessPayment [paymentValid] Submitted --> Cancelled : Cancel / releaseInventory Submitted --> Draft : RequireChanges
state Paid { Paid : entry / confirmInventory }
Paid --> Shipped : Ship Paid --> Refunded : Refund
state Shipped { Shipped : entry / sendTrackingNotification }
Shipped --> Delivered : Deliver Shipped --> Returned : Return
Delivered --> Completed : Finalize Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [] Refunded --> [] Cancelled --> [*]
@enduml
Mermaid Syntax
stateDiagram-v2 [*] --> Draft : Create
state Draft {
direction LR
[*] --> Empty
Empty --> HasItems : AddItem
HasItems --> HasItems : AddItem
HasItems --> Empty : RemoveLastItem
}
Draft --> Submitted : Submit
Draft --> Cancelled : Cancel
Submitted --> Paid : PaymentReceived
Submitted --> Cancelled : Cancel
Submitted --> Draft : RequireChanges
Paid --> Shipped : Ship
Paid --> Refunded : Refund
Shipped --> Delivered : Deliver
Shipped --> Returned : Return
Delivered --> Completed : Finalize
Delivered --> Returned : Return
Returned --> Refunded : ProcessReturn
Completed --> [*]
Refunded --> [*]
Cancelled --> [*]
C# Implementation Patterns
Simple State Machine
public sealed class Order : Entity { public OrderStatus Status { get; private set; }
private static readonly Dictionary<(OrderStatus From, OrderEvent Event), OrderStatus> _transitions =
new()
{
{ (OrderStatus.Draft, OrderEvent.Submit), OrderStatus.Submitted },
{ (OrderStatus.Draft, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.Pay), OrderStatus.Paid },
{ (OrderStatus.Submitted, OrderEvent.Cancel), OrderStatus.Cancelled },
{ (OrderStatus.Submitted, OrderEvent.RequireChanges), OrderStatus.Draft },
{ (OrderStatus.Paid, OrderEvent.Ship), OrderStatus.Shipped },
{ (OrderStatus.Paid, OrderEvent.Refund), OrderStatus.Refunded },
{ (OrderStatus.Shipped, OrderEvent.Deliver), OrderStatus.Delivered },
{ (OrderStatus.Shipped, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Delivered, OrderEvent.Finalize), OrderStatus.Completed },
{ (OrderStatus.Delivered, OrderEvent.Return), OrderStatus.Returned },
{ (OrderStatus.Returned, OrderEvent.ProcessReturn), OrderStatus.Refunded },
};
public Result Transition(OrderEvent @event)
{
if (!_transitions.TryGetValue((Status, @event), out var newStatus))
{
return Result.Failure($"Cannot {@event} order in {Status} status");
}
var oldStatus = Status;
Status = newStatus;
AddDomainEvent(new OrderStatusChangedEvent(Id, oldStatus, newStatus, @event));
return Result.Success();
}
}
public enum OrderStatus { Draft, Submitted, Paid, Shipped, Delivered, Completed, Cancelled, Returned, Refunded }
public enum OrderEvent { Submit, Cancel, Pay, RequireChanges, Ship, Refund, Deliver, Return, Finalize, ProcessReturn }
State Pattern Implementation
public abstract class OrderState { public abstract OrderStatus Status { get; }
public virtual Result Submit(Order order) =>
Result.Failure($"Cannot submit order in {Status} state");
public virtual Result Cancel(Order order) =>
Result.Failure($"Cannot cancel order in {Status} state");
public virtual Result Pay(Order order) =>
Result.Failure($"Cannot pay order in {Status} state");
public virtual Result Ship(Order order) =>
Result.Failure($"Cannot ship order in {Status} state");
protected void TransitionTo(Order order, OrderState newState)
{
order.SetState(newState);
}
}
public sealed class DraftState : OrderState { public override OrderStatus Status => OrderStatus.Draft;
public override Result Submit(Order order)
{
if (!order.HasItems)
return Result.Failure("Order must have items to submit");
TransitionTo(order, new SubmittedState());
order.ReserveInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
TransitionTo(order, new CancelledState());
return Result.Success();
}
}
public sealed class SubmittedState : OrderState { public override OrderStatus Status => OrderStatus.Submitted;
public override Result Pay(Order order)
{
TransitionTo(order, new PaidState());
order.ConfirmInventory();
return Result.Success();
}
public override Result Cancel(Order order)
{
order.ReleaseInventory();
TransitionTo(order, new CancelledState());
return Result.Success();
}
}
Stateless Library Pattern
using Stateless;
public sealed class OrderStateMachine { private readonly StateMachine<OrderStatus, OrderEvent> _machine; private readonly Order _order;
public OrderStateMachine(Order order)
{
_order = order;
_machine = new StateMachine<OrderStatus, OrderEvent>(
() => order.Status,
status => order.SetStatus(status));
ConfigureTransitions();
}
private void ConfigureTransitions()
{
_machine.Configure(OrderStatus.Draft)
.Permit(OrderEvent.Submit, OrderStatus.Submitted)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.OnEntry(() => _order.InitializeOrder());
_machine.Configure(OrderStatus.Submitted)
.PermitIf(OrderEvent.Pay, OrderStatus.Paid,
() => _order.PaymentIsValid)
.Permit(OrderEvent.Cancel, OrderStatus.Cancelled)
.Permit(OrderEvent.RequireChanges, OrderStatus.Draft)
.OnEntry(() => _order.ReserveInventory())
.OnExit(() => { /* cleanup if needed */ });
_machine.Configure(OrderStatus.Paid)
.Permit(OrderEvent.Ship, OrderStatus.Shipped)
.Permit(OrderEvent.Refund, OrderStatus.Refunded)
.OnEntry(() => _order.ConfirmInventory());
_machine.Configure(OrderStatus.Shipped)
.Permit(OrderEvent.Deliver, OrderStatus.Delivered)
.Permit(OrderEvent.Return, OrderStatus.Returned)
.OnEntry(() => _order.SendTrackingNotification());
_machine.Configure(OrderStatus.Delivered)
.Permit(OrderEvent.Finalize, OrderStatus.Completed)
.Permit(OrderEvent.Return, OrderStatus.Returned);
_machine.Configure(OrderStatus.Returned)
.Permit(OrderEvent.ProcessReturn, OrderStatus.Refunded);
// Terminal states
_machine.Configure(OrderStatus.Completed);
_machine.Configure(OrderStatus.Cancelled);
_machine.Configure(OrderStatus.Refunded);
}
public bool CanFire(OrderEvent trigger) => _machine.CanFire(trigger);
public void Fire(OrderEvent trigger) => _machine.Fire(trigger);
public IEnumerable<OrderEvent> GetPermittedTriggers() =>
_machine.GetPermittedTriggers();
}
XState Pattern (TypeScript)
import { createMachine, assign } from 'xstate';
interface OrderContext { items: LineItem[]; customerId: string; paymentId?: string; trackingNumber?: string; }
type OrderEvent = | { type: 'ADD_ITEM'; item: LineItem } | { type: 'REMOVE_ITEM'; itemId: string } | { type: 'SUBMIT' } | { type: 'PAY'; paymentId: string } | { type: 'CANCEL' } | { type: 'SHIP'; trackingNumber: string } | { type: 'DELIVER' } | { type: 'RETURN' } | { type: 'REFUND' };
const orderMachine = createMachine({ id: 'order', initial: 'draft', context: { items: [], customerId: '', } as OrderContext,
states: { draft: { entry: 'initializeOrder', on: { ADD_ITEM: { actions: assign({ items: ({ context, event }) => [...context.items, event.item], }), }, REMOVE_ITEM: { actions: assign({ items: ({ context, event }) => context.items.filter(i => i.id !== event.itemId), }), }, SUBMIT: { target: 'submitted', guard: 'hasItems', }, CANCEL: 'cancelled', }, },
submitted: {
entry: 'reserveInventory',
exit: 'onSubmittedExit',
on: {
PAY: {
target: 'paid',
guard: 'paymentValid',
actions: assign({
paymentId: ({ event }) => event.paymentId,
}),
},
CANCEL: {
target: 'cancelled',
actions: 'releaseInventory',
},
},
},
paid: {
entry: 'confirmInventory',
on: {
SHIP: {
target: 'shipped',
actions: assign({
trackingNumber: ({ event }) => event.trackingNumber,
}),
},
REFUND: 'refunded',
},
},
shipped: {
entry: 'sendTrackingNotification',
on: {
DELIVER: 'delivered',
RETURN: 'returned',
},
},
delivered: {
on: {
RETURN: 'returned',
},
after: {
// Auto-complete after 14 days
'14d': 'completed',
},
},
returned: {
on: {
REFUND: 'refunded',
},
},
completed: { type: 'final' },
cancelled: { type: 'final' },
refunded: { type: 'final' },
},
}, {
guards: {
hasItems: ({ context }) => context.items.length > 0,
paymentValid: ({ event }) => event.type === 'PAY' && !!event.paymentId,
},
actions: {
initializeOrder: () => console.log('Order initialized'),
reserveInventory: ({ context }) =>
console.log(Reserving ${context.items.length} items),
confirmInventory: () => console.log('Inventory confirmed'),
releaseInventory: () => console.log('Inventory released'),
sendTrackingNotification: ({ context }) =>
console.log(Tracking: ${context.trackingNumber}),
},
});
Design Best Practices
State Design Guidelines
-
Name states as conditions: Submitted not Submit
-
Name events as commands: Submit not Submitted
-
Use guards for conditional transitions
-
Keep states atomic: One responsibility per state
-
Document entry/exit actions
-
Consider terminal states (final states)
Common Patterns
Pattern Use Case
Linear Simple sequential flow
Choice Conditional branching
Parallel Concurrent activities
Hierarchical Complex nested states
History Resume from last state
Workflow
When designing state machines:
-
Identify entity: What has the lifecycle?
-
List states: What conditions can it be in?
-
Define events: What triggers state changes?
-
Map transitions: State + Event → New State
-
Add guards: What conditions must be true?
-
Define actions: What happens on transitions?
-
Draw diagram: Visualize for review
-
Implement: Choose appropriate pattern
References
For detailed guidance:
Last Updated: 2025-12-26