第1章:DAGの誤解を解く――「承認=ループ=不可」という錯覚
「業務フローの基盤にDAG(有向非巡回グラフ)を採用したい」
そう提案した瞬間、AIから判で押したように返ってくる言葉がある。「業務には承認があり、その差し戻しがループになります。DAGでは部分的にしか表現できません」
本当にそうだろうか。私たちはあまりにも単純に「逆向きの矢印」を書いてしまい、必要以上に怯えすぎているのではないだろうか。
なぜ「ループ」に見えてしまうのか
DAGが業務フローで敬遠される理由は単純だ。多くの人が描く承認フローのポンチ絵が、こうなっているからである。
[起票・申請] ───> [承認審査] ──(承認)──> [完了]
^ │
└───(差し戻し)──┘
この図を眺めた瞬間、グラフ理論をかじった人間なら誰でも「閉路(Cycle)がある。だからDAG(Acyclic)では扱えない」と判定してしまう。申請と審査をいったり来たりすると扱えない。
だが、現実の業務において、差し戻しを食らった書類は「最初の白紙状態」にタイムスリップしているだろうか? 絶対にそんなことはない。これは「時間」や「世代」を無視して、同じ平面の上で動かした影を見ている錯覚にすぎない。
Unrolling(展開):時間を導入すれば矢印は前にしか向かない
現実に起きている事象を、ありのままに観察してみよう。
差し戻しが発生したとき、申請者は指摘コメントというコンテキストを受け取り、内容を修正して「第2版(Rev.2)」を作成する。承認者は「Rev.1」よりも改良された「Rev.2」を受け取り、審査を開始する。
時間は不可逆であり、文脈は明らかに前に進んでいる。この関係性を素直に時間軸・世代軸に沿って引き伸ばす操作を、コンパイラや最適化の文脈ではUnrolling(ループ展開)と呼ぶ。
Unrollingを適用すると、先ほどの図はこう姿を変える。
[申請 Rev.1] ──> [審査 Rev.1] ──(却下)──> [修正申請 Rev.2] ──> [審査 Rev.2] ──(承認)──> [完了]
ここには逆向きの矢印は跡形もなく、すべてのタスクが未来へ向かって一列に並ぶ。これこそが、業務の現場で実際に起きているのだ。
差し戻しはリセットではなく「次世代の発行」である
差し戻しを「ループ(元の場所に戻る処理)」と捉えると、データは汚れ始める。「現在のステータスを戻す」「前回の入力を上書きする」といった巻き戻し処理が必要になり、状態管理が泥沼化するからだ。Rev.2を申請したのに過去日付で否認されている。
そうではない。差し戻しとは、過去への退行ではなく「次世代(Rev.n)をトリガーにした前進」なのだ。
- ループの視点: 同じ場所をぐるぐる回る「徒労」
- Unrollingの視点: 指摘を糧にして次のイテレーションへ登る「前進」
この認知修正を行えば、「承認や差し戻しがあるからDAGは使えない」という前提そのものが霧散する。DAGは人間系の前進する業務プロセスを表現してきた。承認も排除する理由はない。むしろ、人間が試行錯誤しながら合意へと向かうプロセスは前進であり、嘘偽りなくDAGでモデル化できる構造なのである。
では、こうして前へと伸びていくUnrollingの軌跡を、データベースにはどう刻むべきか。ここでエンジニアの前に立ちはだかるのが、「ワイドテーブルか、イベントログか」という選択肢である。
第2章:ワイドテーブルか、イベントログかは優劣なし――データとDAGの噛み合い方
Unrollingによって不可逆な前進へと姿を変えた承認フロー。では、この次世代へと伸びていく軌跡を、データストアにはどう刻むべきだろうか。
アーキテクチャの議論になると、「イミュータブルなイベント駆動こそが至高」「いや、RDBの正規化が正義」といった宗派争いが起きがちだ。しかし、DAGで業務をオーケストレーションする文脈において、ワイドテーブルとイベントログに本質的な優劣はない。 あるのは「どの複雑さを引き受け、どこで恩恵を回収するか」という設計のトレードオフだけである。
パターンA:1レコードで世界を語る「カラム横持ち(ワイドテーブル)」の美学
エンジニアが「カラム横持ち」と聞くと、正規化を放棄したアンチパターンのように眉をひそめるかもしれない。だが、業務の現場において、1案件を1レコードで完結させるワイドテーブルの破壊力は凄まじい。
[projects テーブル]
case_id | title | v1_status | v1_comment | v2_status | v2_comment | current_rev
--------+---------+-----------+--------------+-----------+------------+------------
#1001 | サーバー購入 | REJECTED | 予算超過です | APPROVED | 承認します | 2
このモデルが圧倒的に強い理由は、「一覧性」と「業務直感性」にある。
- BIや現場ツールへの直結: BIツール(TableauやLooker)、あるいは現場が愛用するスプレッドシートに連携する際、JOINや複雑な集約クエリを一切必要としない。1行引けば、案件の現在地も過去の指摘もすべてそこに並んでいる。
-
トランザクションのシンプルさ:
「この案件のこの版を更新する」という操作が、単一レコードへのアクセスで完結する。
もちろん、良くない部分として横持の限界、つまり「差し戻し何回分のカラムを用意する?」という問題だ。しかし、現実の社内業務において「差し戻しは最大3回まで。それ以上は却下して起票し直し」といった運用ルールで回避できるなら、カラム横持ちは結果的に合理的で、現場を一番幸せにする現実解になり得る。
パターンB:ファクトを淡々と積む「イベントログ」の洗練
一方、差し戻しの回数を事前に規定せず、プロセスの変更可能性を受けれる間口の広さを考えると、純粋に関心の分離を突き詰め、プロセスを細切れにした「イベントログ」が威力を発揮する。
[workflow_events テーブル]
timestamp | case_id | rev | event_type | payload
----------+---------+-----+-------------+-------------------------------------
10:00 | #1001 | 1 | SUBMITTED | {"amount": 1200000}
10:30 | #1001 | 1 | REJECTED | {"reason": "予算超過です", "by": "Sato"}
11:00 | #1001 | 2 | RESUBMITTED | {"amount": 980000}
11:15 | #1001 | 2 | APPROVED | {"by": "Sato"}
一見するとデータはバラバラの断片に見える。しかし、業務の視座で見れば何の破綻もない。
-
case_idとrevで完全に繋がる:
WHERE case_id = '#1001' ORDER BY rev, timestampで引くだけで、一本の不可逆なストーリーが決定論的に再現される。 -
スキーマの不変性:
差し戻しが10回発生しようが、テーブル設計を変更する必要は1ミリもない。 -
DAGの実行モデルとの完全一致:
UnrollingされたDAGの各タスクは、「新しいイベントを1行追記(INSERT)する」だけで責務を果たせる。過去の行をロックしたり上書きしたりするリスクから完全に解放される。
もっともこれはDB側が未来を考えなくて済むメリットがある一方で、BI側にはデータ整理のタスクがまとめてやってくるという課題先送り型の解決でもある。
どちらを選んでも、BPMNやステートマシンよりDAGが勝る理由
ワイドテーブル(横に伸ばす)にするか、イベントログ(縦に積む)にするか。それはアウトプットの用途(BIでの即時可視化を優先するか、スキーマの疎結合を優先するか)で決めればいい。
重要なのは、データの持ち方がどちらであっても、オーケストレーションの主翼にはDAGを据えるべきだという点だ。
BPMNエンジンや巨大なステートマシンフレームワークを導入すると、システムは途端に「ステートの迷宮」と化す。
- 「ステータスがAかつフラグがBのとき、差し戻しボタンを押したらどの状態に遷移すべきか?」
- 「この状態遷移は、どのテーブルのどのロックを取って保証するのか?」
こうした泥臭い状態管理ロジックが、アプリケーションコードやBPMNのXML定義の中に蜘蛛の巣のように張り巡らされていく。
DAGを選べば、この悪夢から脱出できる。
DAGが表現するのは「タスク間の依存関係(何が終わったら次が動くか)」だけだ。状態の確定や履歴の保持は、背後のテーブル(ワイドであれイベントであれ)に委ねればいい。
タスクの責務が「前のノードの結果を受け取り、処理して、結果を書き込む」という単一の方向に純化されるため、システム全体の複雑度は劇的に下がるのである。
では、このシンプルで力強いDAGの上に、承認という「いつ終わるか分からない人間系の揺らぎ」を具体的にどう組み込むのか。その答えが、第3章で解説する「サブグラフによるスパイラルフロー」である。
第3章:業務整理の新標準――サブグラフが描く「スパイラルフロー」
承認をunrollingすれば逆転しないとはいえ、それをDAGで表現するには、一つのテクニックが必要になる。そこで不可欠となるのが、承認プロセスを「サブグラフ」へと切り分ける設計である。
メインプロセス中に「承認サブグラフ」という、カプセル化したプロセスを書く。これだけでDAGは守られる。
親DAGは大動脈を走らせ、サブグラフで合意を紡ぐ
このアプローチの本質は、「業務の進行」と「合意の形成」という関心事の完全な分離にある。
-
親DAG(大動脈):
「見積フェーズ」「契約フェーズ」「発注フェーズ」といったマクロな依存関係だけを管理する。親から見れば、承認プロセスは「合意が完了した」という単一の成果物を返すブラックボックス(1つのノード)にすぎない。 -
サブグラフ(承認プロセス):
親から切り離された独立したスコープの中で、DAGではないグラフ構造を構築する。つまりここだけループを扱えるのです。既存の呼び名としては状態遷移グラフ(state transition graph)、形式化すれば 有限状態機械(finite-state machine / FSM)と言えます。
大動脈の流れを人間系の揺らぎで汚染させない。副作用や不確実性の高い処理をサブグラフという「境界の向こう側」に隔離することで、パイプライン全体の骨格はどこまでも堅牢に保たれる。
二次元の円環を脱し、三次元へ登る「スパイラルフロー」
では、サブグラフの内部はどうなっているのか。完全に循環するタイプの状態遷移グラフと承認プロセスは性質が違う。承認プロセスを表現する名前が「スパイラルフロー(Spiral Flow)」である(今決めた)。
2次元の平面図で見れば、申請と承認は同じ場所をぐるぐる回っているように錯覚する。しかし、リビジョン(世代)という高さを加えた3次元空間で見渡せば、その軌跡は決して閉じた輪(ループ)を描いてはいない。
[承認 v2] ──> (合意完了:親DAGへ復帰)
^
│ (上へと巻き上がる)
[修正申請 v2]
^
│
[却下 v1]
^
│
[申請 v1]
指摘を受けるたびに知見が反映され、成果物の精度が高まり、1段高いレイヤーへと巻き上がっていく。過去の座標に戻ることなく、未来へ向かって前進し続ける螺旋構造。これがスパイラルフローだ。
「差し戻し=ループ」という言葉の呪縛を捨て、「スパイラルを描いて上昇するプロセス」として捉え直したとき、サブグラフの内部はリビジョンなどを利用することで実質的にDAGとして理解できる。
そしてデータの記録はワイドとイベント記録の中間的になる。差し戻しで横に広がるのではなく、リビジョンが変わった新しいレコードとしてスパイラルが重なる。
DAGは「時間」を持たない――だからこそ業務に強い
DAGは基本的に時間軸を持たない。 DAGが関知するのは「依存関係(Aが終わらなければBは動かない)」という論理的な順序関係だけだ。そして状態遷移も時間軸を持たない。だから状態遷移はループだと言われる。これはシンプルさを優先したDAGの弱点とも言える。
しかし状態遷移にリビジョン管理を加えることで、実質的にはDAG的に扱える。間違いなくunrollingできるからだ。
圧倒的な保守性と「業務の可観測性(Observability)」
業務フローをサブグラフによるスパイラルフローとし扱うことで、システムは従来のワークフローでは得られなかった圧倒的な可観測性を手に入れる。
BPMNや巨大な状態遷移テーブルでは、「今、案件がどこで止まっているのか」「なぜ止まっているのか」を把握するために、複雑なログを追跡しなければならなかった。中身がブラックボックス化しやすいのだ。
しかしDAGであれば、「どの案件が、サブグラフ内のスパイラルのどの世代(Rev)のどのノードで待機しているか」が、グラフのトポロジーとして一目で可視化される。
- パイプラインを見れば、詰まっているボトルネックが即座に特定できる。
- 不具合が起きても、影響を受ける後続ノードが確定的に把握できる。
- タスクの責務が単一方向であるため、テストも改修もノード単位で安全に行える。
「承認はループなのだからDAGでは扱えない」
この言説は、業務の本質を平面的にとらえループと見誤った亡霊にすぎない。差し戻しを不可逆なスパイラルフローとして捉え、承認プロセスはサブグラフとして完結させる。そしてデータはスパイラルとして積み重なり、その螺旋の歩みを忠実に記録する。
業務がループに見えても、人間の歩みを確実に処理する。DAGこそが、業務プロセスを最もエレガントに表現できるツールである。