0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: デジタル創作サークルUniProject
UniProject公式HP

はじめに

業務システムを作っていると、データの「ステータス(状態)」は必ず出てきます。

draft (下書き)
review (レビュー中)
approved (承認済み)
published (公開済み)
rejected (却下)
archived (アーカイブ)

これが5個、6個と増えていくと、だんだん仕様の怪しい「裏ルート」が生まれ始めます。

「あれ、API直接叩いたら下書きからいきなり公開状態にできちゃわない?」
「公開済みになったデータを再編集したら、ステータスはどうなるの?」
「アーカイブしたものを元に戻すボタンがないんだけど、バッチで戻したらバグる?」

こういう曖昧さを放置したままコードを書くと、後で痛い目を見ます。
この記事では、この手の「ありえないステータス遷移」を、モデル検査(形式検証)の考え方を使って設計段階で潰す方法を整理します。


許す遷移だけを「ホワイトリスト」で書く

まず、一番基本となるアプローチです。
「禁止事項」を書くのではなく、「これとこれだけは遷移してヨシ」というホワイトリストを書きます。

図にすると分かりやすいですね。
基本ルールは 「この矢印がない遷移は、絶対にすべて禁止」 です。


ツールに「悪い裏ルート」を探させる

もしこの遷移のルールを雑に実装してしまったとします。
モデル検査ツール(AlloyやTLA+など)に「公開の条件」を探索させると、こんな反例を叩き出してきます。

COUNTEREXAMPLE
  trace: draft -> publish_directly
  state: WorkflowState(status='published', approved_by=None)

見事に「承認ルートのすり抜け」が発覚しました。
下書きからいきなり公開されていて、しかも approved_by が空っぽです。コンプライアンス的に大問題になるやつですね。

ツールを使わなくても、設計レビューで「この矢印を無視して遷移できる抜け道はないか?」と自問自答するだけで、こういうバグはかなり防げます。


不変条件(絶対に守るルール)を宣言する

ステータス設計では、Excelに巨大な「〇×遷移表」を書いて満足しがちですが、人間は必ず表を見落とします。

遷移表だけでなく、システム全体として「これだけは絶対に守り抜く」という不変条件を言葉にしておくことが大事です。

【不変条件】
・ published (公開済み) に到達するデータは、必ず過去に approved (承認) を通っていること
・ archived (アーカイブ) 状態に入ったデータは、二度と他の状態には戻らないこと
・ rejected (却下) されたデータは、そのまま直接 published になることは絶対にない

こういうルールを明文化しておくと、単体テストで「下書きから直接公開APIを叩いたら弾かれるか」というテストケースが自然に生まれます。


実務でバグらせない実装パターン

ステータス遷移のバグは、「遷移の判定ロジックがコードのあちこちに散らばる」ことで発生します。

1. 遷移ガードを1箇所に集める

コントローラーごとに if 文を書くのはやめましょう。遷移表そのものをコードで表現します。

const allowedTransitions = {
  draft: ["review"],
  review: ["approved", "rejected"],
  approved: ["published"],
  rejected: ["draft"],
  published: ["archived"],
  archived: [],
};

function canTransit(fromStatus, toStatus) {
  return allowedTransitions[fromStatus].includes(toStatus);
}

すべてのAPI処理は、必ずこの canTransit を通るように強制します。

2. 「管理者だけの例外」に気をつける

「基本はダメだけど、管理者権限ならどのステータスからでもアーカイブできるよ」
こういうビジネス要件が入ると、遷移表が一気にカオスになります。

この場合は、通常ユーザーの遷移表と管理者の遷移表を分けるか、例外操作用の専用APIを作って切り離すのが安全です。

3. バッチ処理の「直接UPDATE」を許さない

API側はしっかり遷移チェックを入れたのに、夜間バッチが空気を読まずにDBを UPDATE status = 'published' と直接書き換えてしまい、不正状態が生み出される。あるあるです。

バッチ処理も必ずアプリケーションのドメインロジック(canTransit)を通すようにするか、DBのトリガーや制約で物理的に防ぐ必要があります。

4. ステータスとフラグを混ぜない

status = 'canceled' というステータス管理と、canceled_at IS NOT NULL というフラグ管理が別々に存在すると、いつか必ずズレて地獄を見ます。
「状態の正」をどこに置くかは、設計段階で決めておきましょう。


遷移に条件を付ける

実際のワークフローでは、単に from -> to だけでは足りないことがあります。

たとえば、

review -> approved

という遷移があっても、誰でも承認できるわけではありません。

条件があります。

承認者ロールを持つ
自分が作成した申請ではない
必要な添付ファイルがある
期限内である

つまり、遷移はこうなります。

from
to
condition
effect

この condition を曖昧にすると、ありえない遷移が混ざります。


遷移の副作用

状態遷移には副作用がつくことがあります。

approved -> published:
  公開日時をセット
  通知を送る
  検索インデックスを更新

この副作用も仕様です。

状態だけ変わって副作用が失敗したら、どう扱うのでしょうか。

published なのに検索に出ない
approved なのに通知されない
archived なのに公開ページが残る

こういう状態がありえるなら、モデルに副作用状態を足すことがあります。


例外遷移をどう扱うか

業務には例外があります。

管理者だけは published -> draft に戻せる
緊急時だけ approved を飛ばせる
期限切れなら自動で rejected になる

例外を雑に入れると、遷移表が壊れます。

おすすめは、例外も明示的な遷移として書くことです。

published -> draft
condition: role = admin and reason is present
effect: audit_log を残す

例外は「例外だから仕方ない」ではなく、条件付き遷移として扱います。


監査ログとの関係

ワークフローでは、誰が遷移させたかが重要です。

from_status
to_status
actor_id
reason
request_id
created_at

これを残しておくと、あとで追えます。

特に管理者例外や手動修正がある場合、監査ログは必須に近いです。

形式検証では「不正遷移しない」を見ます。
監査ログでは「何が起きたか追える」を守ります。

両方必要です。


テストに落とす

遷移表があるなら、テストはかなり書きやすくなります。

許可される遷移は成功する
許可されない遷移は失敗する
遷移時の副作用が1回だけ起きる
権限がないユーザーは遷移できない

全組み合わせを表で回すだけでも強いです。

さらにモデル検査で反例が出たなら、その反例をテストにします。

draft -> published が直接できない
archived -> review に戻れない

のようなテストです。


状態を増やしすぎない

ワークフロー設計では、状態を増やしすぎるとつらくなります。

paid
paid_but_mail_failed
paid_but_stock_failed
paid_but_point_failed

のように、全部をステータスに入れると爆発します。

状態として持つべきものと、副作用の進捗として持つべきものを分けます。

order_status
payment_status
notification_status

のように分けたほうが分かりやすいこともあります。


まとめ

ワークフローのシステムでは、

・ 許す遷移(ホワイトリスト)を明示する
・ 「公開前に承認を通る」「アーカイブ後は戻らない」といった不変条件を定義する

これだけで、設計の解像度が跳ね上がります。

「とりあえずステータスを持たせて、あとからAPIで書き換えればいいや」と実装から入るのはかなり危険です。
コードを書く前に、ホワイトボードでもいいので「状態遷移図」と「不変条件」を書く。形式検証のこのエッセンスを取り入れるだけで、安全なワークフローが作れるようになります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?