mcpField
S
state-machine-design

state-machine-design

github.com/melodic-software開発28101 回インストール今日更新
リポジトリ

state-machine-design について

state-machine-design は、ライフサイクル、ワークフロー、システム動作をモデル化する有限状態機械とステートチャートの設計スキルです。基本概念に加え、PlantUML、Mermaid、C#、TypeScript の記法や実装パターンを紹介します。

主な機能

  1. 01基本要素と状態の種類の解説
  2. 02PlantUML と Mermaid の記法
  3. 03C# と TypeScript の実装パターン
  4. 04設計から実装までの手順

活用シーン

  1. 01エンティティのライフサイクルのモデル化
  2. 02条件分岐を含むワークフローの設計
  3. 03システム動作の図示とレビュー

MCP 対応

設計前に MCP サーバーを使って実装パターンを確認するよう求めており、XState の例として context7 を挙げています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/melodic-software/claude-code-plugins --skill state-machine-design

インストール後、Claude Code で/state-machine-designを入力して読み込まれたか確認します。

詳細

ステートマシン設計スキル

このスキルを使用する場面

次のような場合に、このスキルを使用します。

  • ステートマシン設計タスク - ライフサイクルや振る舞いの仕様策定に向けて、ステートチャートやステートマシンのモデリングに取り組む場合
  • 計画または設計 - ステートマシン設計のアプローチに関するガイダンスが必要な場合
  • ベストプラクティス - 確立されたパターンや標準に従いたい場合

概要

エンティティのライフサイクル、ワークフロー、システムの振る舞いをモデル化するための有限ステートマシンとステートチャートを設計します。

必須:ドキュメントを優先するアプローチ

ステートマシンを設計する前に、次のことを行います。

  1. docs-management スキルを呼び出し、ステートマシンのパターンを確認します
  2. MCP サーバーを介して実装パターンを確認します(XState には context7 など)
  3. すべてのガイダンスを Harel のステートチャート意味論に基づかせます

ステートマシンの概念

基本要素
要素説明例
状態システムが取り得る状態Draft、Submitted、Paid
遷移ある状態から別の状態への変化Draft → Submitted
イベント遷移のトリガーSubmit、Pay、Cancel
ガード真でなければならない条件[hasItems]、[isValid]
アクション遷移に伴う副作用sendNotification、updateDatabase
入場アクション状態に入るときのアクションonEnter: startTimer
退出アクション状態から出るときのアクションonExit: stopTimer
状態の種類
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
}

ステートマシンの記法

PlantUML 構文
@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 構文
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# の実装パターン

シンプルなステートマシン
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
}
ステートパターンの実装
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 ライブラリのパターン
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 パターン(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}`),
  },
});

設計のベストプラクティス

状態設計のガイドライン
  1. 状態には条件を表す名前を付けます:Submit ではなく Submitted
  2. イベントにはコマンドを表す名前を付けます:Submitted ではなく Submit
  3. 条件付き遷移にはガードを使用します
  4. 状態はアトミックに保ちます:1 つの状態につき責務は 1 つです
  5. 入場アクションと退出アクションを記録します
  6. 終端状態(最終状態)を検討します
よくあるパターン
パターン使用例
線形シンプルな逐次フロー
選択条件分岐
並列並行アクティビティ
階層複雑な入れ子状態
履歴最後の状態から再開

ワークフロー

ステートマシンを設計する際は、次の手順に従います。

  1. エンティティを特定します:ライフサイクルを持つのは何ですか?
  2. 状態を列挙します:どのような状態を取り得ますか?
  3. イベントを定義します:何が状態変化を引き起こしますか?
  4. 遷移を対応付けます:状態 + イベント → 新しい状態
  5. ガードを追加します:どのような条件が真でなければなりませんか?
  6. アクションを定義します:遷移時に何が起こりますか?
  7. 図を作成します:レビュー用に可視化します
  8. 実装します:適切なパターンを選択します

参考資料

詳しいガイダンスについては、次を参照してください。


最終更新日: 2025-12-26

コメント