ソフトウェア設計

Single Source of Truth、同じ情報を2か所に置けば必ず食い違う

リリース直前に見つかるバグには典型的なものがあります。フロントエンドは割引率を10%、バックエンドは15%で計算する、文書では必須のフィールドが実際のAPIから消えている、といったケースです。コード自体はどちらも正常です。問題は同じ情報が2か所にあり…

読了 5 分
Single Source of Truth、同じ情報を2か所に置けば必ず食い違うのカバー画像

リリース直前に見つかるバグには典型的なものがあります。フロントエンドは割引率を10%、バックエンドは15%で計算する、文書では必須のフィールドが実際のAPIから消えている、といったケースです。コード自体はどちらも正常です。問題は同じ情報が2か所に存在し、一方だけが変更されたことです。

こうした事故を構造的に防ぐ原則がSingle Source of Truth、略してSSOTです。名前は大げさですが、内容は一文で済みます。すべての情報片には、権威ある原本が正確に1つだけ存在しなければならない。

要点をまとめます。

  1. SSOTは「情報の原本は1つ」という原則であり、保存先を1つに限定する意味ではない
  2. 重複した情報は必ず食い違う。問題は食い違った瞬間ではなく、どちらが正しいか誰にも分からなくなる瞬間だ
  3. コピーが必要なら「派生物」として作る。手で2回書かず、原本から自動生成する
  4. キャッシュや複製はSSOT違反ではない。原本の場所と更新方向が明確ならよい

なぜ重複は必ず食い違うのか

情報を2か所に置いた瞬間、同じ状態を保つ責任は人に移ります。ツールもコンパイラもその約束を知らないからです。修正のたびに「この値はほかのどこにあったっけ」と覚えておく必要があり、一度でも忘れれば2つの値は別の道を進みます。

さらに厄介なのは、食い違った後です。コード2か所で定数が異なる値になっていても、どちらが正しいかはコードだけでは分かりません。gitの履歴を調べ、当時の担当者を探し、企画文書を確認する考古学が始まります。SSOT崩壊の本当のコストはバグ修正ではなく、「どれが真実か」を判定する時間です。

よくある例をいくつか見てみましょう。

  • 定数の重複: 最大アップロードサイズ10MBを、フロント検証・バックエンド検証・エラーメッセージにそれぞれハードコード
  • 検証ロジックの重複: メール形式の検証を、クライアントとサーバーが異なる正規表現で実行
  • 文書とコード: API仕様書と実装が別々。数か月後、文書は小説になる
  • DBとキャッシュ: 原本更新後のキャッシュ無効化を忘れ、古いデータを配信し続ける
  • デザインとコード: デザイン案の色値とアプリにハードコードした色値が微妙に異なる

形は違っても構造は同じです。原本が2つあり、同期が人の手に依存しています。

SSOTなしの手動同期で値が食い違う構造と、共有原本から派生する構造の比較図
人の記憶に頼る同期と、原本から派生させる構造の違いです

原則は「コピー禁止」ではなく「派生」

SSOTを「情報は1か所だけに保存すること」と誤解しがちです。するとキャッシュもリードレプリカもビルド成果物も違反に見えます。実際の原則は異なります。複数か所に存在してもよい。ただし原本は1つで、残りはすべて原本から派生しなければならない。

派生物を作る代表的な方法がコード生成です。

  • スキーマから型を生成: OpenAPI仕様からクライアント型とサーバースタブを生成すれば、仕様が原本となり、文書とコードが食い違う余地がなくなる
  • デザイントークン: 色とタイポグラフィを1つのトークンファイルに定義し、iOS・Android・Webコードを個別に生成する
  • 共有定数モジュール: フロントエンドとバックエンドが同じパッケージから定数をimportするようにし、ハードコードのコピーを根本から防ぐ
  • コードから文書を抽出: コメントや型からAPI文書を生成すれば、文書は常にコードに追従する

共通しているのは、「人が2回書く」箇所を「機械が1回生成する」箇所に変えることです。同期の責任が人の記憶からビルドパイプラインへ移り、食い違えばCIが先に気づきます。

キャッシュや複製も同じ枠組みで整理できます。原本の場所が宣言され、更新が原本→コピーの一方向だけに流れ、コピーが古くなり得る時間(TTL・無効化戦略)が定義されていれば、SSOTは守られています。違反は「コピーが存在するとき」ではなく、「コピーを直接変更し始めたとき」に起きます。

組織にも同じ原則を適用する

SSOTはコードだけの話ではありません。マイクロサービス設計で「このデータの所有者はどのサービスか」を決めること、社内Wiki・Notion・Slackに散在するポリシー文書から「公式版」を指定することも、すべて同じ問題です。

特に文書はコードより食い違いやすいものです。コンパイラもテストもないからです。だから組織レベルのSSOTでは、ツールより合意が先です。「オンボーディング手順の原本はWikiのこの1ページで、ほかにはリンクだけ置く」といったルールです。コピーの代わりにリンクを置くことが、文書世界の派生です。

スキーマ原本から型・文書・デザイントークンを自動生成するコード生成パイプラインのイラスト
人が2回書く箇所を、機械が1回生成する箇所に変えます

実践チェックリスト

明日から適用できる判断基準にまとめると、次のとおりです。

  1. 同じ値を2回目に入力する瞬間がサイン: 定数・検証ルール・設定値をコピーしているなら、共有モジュールへ移すか生成に変えられないか、まず検討する
  2. 原本を宣言する: キャッシュ・複製・要約自体は問題ではない。「原本はここ」とコードと文書に明記され、更新が一方向か確認する
  3. 食い違いを人ではなくツールに検出させる: スキーマ検証、契約テスト、生成コードのdiffチェックをCIに入れれば、同期失敗がマージ前に判明する
  4. 完璧主義には注意する: すべての重複をなくそうと過剰な抽象化を作れば、それ自体がコストになる。頻繁に変わる情報や、食い違うと損失が大きい情報からSSOTを整える

リファクタリングでいうDRY(Don’t Repeat Yourself)原則と根は同じです。DRYがコードロジックの重複を対象にするのに対し、SSOTはデータや知識の重複まで広げた上位原則といえます。次に「この値はあちらにもあるが、一緒に直すべきか」と思ったら、そこがSSOTを整える場所です。