前置き
今回は、CI/CDやデータパイプラインを「単なるスクリプトの連なり」から、
「信頼性と保守性の高い、堅牢なソフトウェアアーキテクチャ」
へと昇華させるため、必要な設計思想を書いていきます。
すべてを基礎として有機的につなげ、パイプラインの保守性や可観測性を向上させていきましょう。
契約による設計 × パイプライン
ソフトウェア工学におけるDbCは、ソフトウェアのコンポーネント(この場合はパイプラインの各ステップ)が、お互いに 「契約」 を結んで動作することを前提とします。
この「契約」は、主に以下の3つの要素で構成されます。
1. 事前条件 (Preconditions)
パイプライン中のステップ2の事前条件として、ステップ1のチェックが全て通っていること
意味
ステップ2(消費者)が、ステップ1(生産者)に対して
「この処理を実行する前に、あなたは最低限この品質を保証してください」
と要求する契約です。
例
・ステップ1が生成したファイルが存在する。
・ステップ1が生成したデータのスキーマが正しい。
・ステップ1の適応度関数(例:テストカバレッジ)が閾値を満たしている。
これらが、次のステップ2を開始できる事前条件です。
2. 不変条件 (Invariants)
ステップ2が実行中常に満たすべき条件。
意味
そのステップが実行されている間、常に真でなければならないシステムの「健康状態」に関する契約です。
例
・処理時間が、設定されたタイムアウト(例:10分)を超えていない。
・メモリやCPUの使用率が、設定された閾値を超えていない。
・(ストリーム処理の場合)処理の遅延(ラグ)が、SLA(例:30秒)を超えていない。
3. 事後条件 (Postconditions)
意味
ステップ2が、後続のステップ(あるいは最終的な利用者)に対して
「私の処理が正常に完了したら、この品質を保証します」
と約束する契約です。
例
・ステップ2が生成した出力データが、定義されたスキーマを満たしている。
・出力データに、NULLやマイナス値などの不正な値が含まれていない。
・機密情報がマスキングされている。
なぜこれがパイプラインの保守性を劇的に向上させるのか
DbCをパイプラインに適用すると、保守性が飛躍的に向上します。
1. 障害検知と原因特定が瞬時に行える
これが最大のメリットです。
パイプラインが失敗したとき、「誰が契約を破ったか」 が即座に分かります。
「事前条件」で失敗→ステップ1(生産者)が悪い。
「不変条件」or「事後条件」で失敗→ステップ2(消費者)の内部ロジックが悪い。
これにより、パイプラインのデバッグと修復時間(MTTR)が劇的に短縮されます。
2. 変更が安全になる (OCP/ECRSの実践)
パイプラインの各ステップが「契約」によってカプセル化されます。
ステップ2をリファクタリング(ECRS)したい場合、
開発者は、「事前条件を満たし、事後条件を守る」 ことだけに集中すればよくなります。
内部の実装を(例えばPythonからGoに)書き換えても、この契約さえ守れば後続のステップに影響を与えません。
さらに、新しいステップ3を追加(OCP)したい場合、ステップ2の「事後条件」が、ステップ3の「事前条件」を満たしているかを確認するだけで済みます。
3. 責務が明確になる
「このステップは何を保証し、何を期待するのか」がコード(YAMLや設定)として明文化されます。
これにより、「なんとなく動いている」という属人化したパイプラインから、誰でも保守・改善できるアーキテクチャへと進化します。
ステートマシン図 × パイプライン
パイプラインで処理されるリリース対象(成果物、Artifact)の状態をステートマシン図として明確に定義し、各ステップで想定される状態遷移を検証・強制することは、パイプラインの安全性と信頼性を確保する上で極めて重要です。
これは、上記の「契約による設計(DbC)」を、成果物のライフサイクルという時間軸に適用したものです。
なぜステートマシンが重要なのか? (工場のアナロジー 🏭)
CI/CDパイプラインは、製品(ソフトウェア)を作る自動化された工場です。
成果物 (Artifact)
工場で組み立てられている製品(例: 車)
パイプラインのステップ
各工程(例: 溶接、塗装、検査)
ステートマシン
製品が各工程を経て「どの状態にあるべきか」を定義した品質管理基準(例: 溶接済, 塗装済, 検査合格)
メカニズムのアナロジー
もし、「塗装」工程に「溶接済」ではない部品(想定しない状態変化)が流れてきたら、
工場はそのラインを即座に停止させますよね?
そうしないと、不良品が出荷されてしまうからです。
CI/CDパイプラインもまったくメカニズムは同じです。
「セキュリティスキャン」のステップに、「テスト合格済」ではない成果物が渡されたら、何かが根本的に間違っています。
よって、そのままデプロイに進むのは極めて危険です。
パイプラインにおけるステートマシンの実装
これをパイプラインアーキテクチャで実現するには、以下の要素が必要です。
1. 状態の定義
まず、成果物が取りうる「状態」を明確に定義します。
例として、
SOURCE_CHECKED_OUT, BUILT, UNIT_TESTED, STATIC_ANALYZED, READY_FOR_INTEGRATION, INTEGRATION_TESTED, SECURITY_SCANNED, READY_FOR_STAGING, DEPLOYED_TO_STAGING, STAGING_TESTED, READY_FOR_PRODUCTION, DEPLOYED_TO_PRODUCTION, OBSERVED_STABLE
などなど。
2. 各ステップの「契約」としての状態遷移
パイプラインの各ステップ(ジョブ)は、自身の 事前条件として「期待する入力状態」を、
事後条件として「保証する出力状態」を定義します。
例 (security_scan ジョブ)
・事前条件: 成果物の状態が INTEGRATION_TESTED であること。
・実行:スキャンを実行する。
・事後条件:成果物の状態を SECURITY_SCANNED に遷移させる。
3. 検証と停止メカニズム
各ステップの開始時に、渡された成果物の現在の状態が、そのステップの事前条件と一致するかを検証します。
一致しない場合には、即座にパイプラインを失敗させ、デプロイを停止します。
これは、パイプラインのロジック自体にバグがあるか、手動で不正な操作が行われた可能性を示す重大なアラートだからです。
結論
パイプラインにおける成果物の状態をステートマシンとしてモデル化し、各ステップでその状態遷移を厳格に検証することは、以下のメリットをもたらします。
安全性の向上
テストやスキャンなどの重要な品質ゲートが意図せずスキップされることを防止します。
可読性と保守性の向上
パイプラインが「何をしているのか」「何を保証するのか」が、状態遷移として明確に文書化されます。
デバッグの容易化
パイプラインが停止した際、「どのステップ」で「どの状態遷移」に失敗したのかが即座に分かり、原因特定が容易になります。
これは、単にプロセスを自動化するだけでなく、そのプロセス自体の正当性と安全性を保証するための、極めて重要な設計プラクティスです。
イベントソーシング × パイプライン
扱う成果物が、まだシンプルなモノリシックアーキテクチャのものなら、以下のような
一本のエンドツーエンドのパイプラインで済むのでいいんです。
問題は、モジュラーモノリスとかにしないといけなくなってきてからです。
モジュラーモノリス構造の際には、以下のような「ファンインパイプライン」構造を取り、
各モジュールは別々にパイプラインを通っていき、集約されるスタイルを取ります。
このファンインパイプラインのように、複雑さが増す(=複数のプロセスが集約される)アーキテクチャにおいてこそ、各ステップの状態変化を記録するイベントソーシングの考え方は、トラブルシューティングを劇的に容易にするための最も強力な武器となります。
なぜイベントソーシングが「最強の武器」になるのか
各ステップでの状態変化を(例: Pipe-A: BUILT, Pipe-A: TEST_PASSED)のようにイベントとして記録し続けることは、パイプライン上の 「フライトデータレコーダー(航跡記録器)」 を持つことと等価です。
この「何が起きたか」の 不変のログ が存在することにより、問題が発生した際の原因調査が非常に行いやすくなります。
1. シナリオA:「デプロイできない」(事前)
問題
ファンインパイプラインの最後の「集約デプロイ」ステップが失敗した。
原因
10本のパイプラインのうち、どれがデプロイの「事前条件」を満たしていないのかが分からない。
イベントソーシングによる解決
イベントログ(状態DB)をクエリするだけで、即座に原因が特定できます。
メリット
推測や関係者へのヒアリング、その際の調整コストは不要です。
「Pipe-Cがセキュリティスキャンに失敗したため、集約デプロイが契約違反(事前条件の不履行)として停止した」という事実が即座に判明するからです。
2. シナリオB:「問題がある状態でデプロイ」(事後)
問題
本番環境で「注文ができない」という障害が発生。
原因
どのデプロイがこの問題を引き起こしたのか、いつから発生したのかが分からない。
イベントソーシングによる解決
イベントログは、完璧な監査証跡(Audit Trail)およびトレーサビリティを提供します。
①. まず、「DEPLOYED_TO_PRODUCTION」イベントを時系列で確認します。そこで「障害発生(14:00)の直前にデプロイされたのは、13:55のPipe-A (v1.5.0)とPipe-B (v3.1.0)だ」と特定します。
②. 次に、そのデプロイに関連する 「状態遷移の“全履歴”」 をドリルダウンします。
③. Pipe-A (v1.5.0)の履歴を見ると、STATE: INTEGRATION_TEST_PASSEDが記録されている。
④. Pipe-B (v3.1.0)の履歴を見ると、STATE: INTEGRATION_TEST_SKIPPED (!!!) という異常な状態遷移が記録されているのを発見します。
メリット
なぜ問題が起きたのか(=Pipe-Bがテストをスキップしたままデプロイされたから)が定量的な証拠をもって判明します。
これにより、
サービス復旧時間(MTTR)が劇的に短縮される
だけでなく、
「なぜテストがスキップされたのか?」 という、パイプラインアーキテクチャ自体の根本原因の改善
にも繋がります。
結論
ファンインパイプラインは、その集約性ゆえに「何が起きているか」がブラックボックス化しやすい危険性をはらんでいます。
ゆえに、「各ステップの状態変化をイベントソーシングとして記録する」アプローチは、
そのブラックボックスに 完璧な「可観測性(Observability)」 を与え、
トラブルシューティングを運用者の「推測ゲーム」から「事実の確認作業」へと変える、
最も効果的な設計といえるでしょう。


