mybook

エピローグ — 意思決定の積み重ねがアーキテクチャになる

1年後の朝

シンジはコーヒーを飲みながら、docs/adr/ ディレクトリを開いた。

$ ls docs/adr/*.md | wc -l
      58

58本。1年間で書いたADRだ。

最初は5本から始まった。遡及ADRを書いてチームの過去を掘り起こした。そして新しい判断のたびに、1本ずつ積み上げてきた。

$ git log --oneline docs/adr/ | head -20
a4f8c21 docs: ADR-058 gRPCによるサービス間通信の検討(見送り)
b9e3d18 docs: ADR-057 Datadogの導入とNewRelic契約終了
c2a1f09 docs: ADR-056 フィーチャーフラグにFlipperを採用
d7b3e2a docs: ADR-055 PostgreSQL 16へのバージョンアップ
e8c4f1b docs: ADR-054 モバイルAPIのレートリミット設計
f1a2b3c docs: ADR-053 S3マルチリージョン対応(見送り)
...

1年前、「なぜそうなっているのか誰も知らない」という問題から始まった。今は違う。

今では誰でも言える。「なぜそうなっているかはADRに書いてある」と。

転機となった3つの瞬間

1年を振り返って、シンジはADRが「本当に機能した」と感じた3つの瞬間を思い出した。

瞬間1: 新メンバーの即戦力化

8ヶ月前、シニアエンジニアのナオが入社した。前職はGoエンジニア。Railsは初めてという状況だった。

入社初日、シンジはナオに言った。「まず docs/adr/README.md を読んで。次に001から010まで読んでみて。それからコードを読むと構造が分かりやすいはず」

3日後、ナオから連絡が来た。

「ADRを読んで、なぜこのアーキテクチャなのかがほぼ分かりました。特にADR-008のPostgreSQLへの移行理由と、ADR-015のECS継続判断が明確で、インフラの意思決定の背景が分かった。あとは実際のコードを読んで細部を確認するだけです。一つ聞いていいですか。ADR-031でSidekiqに移行した後のGoodJobのコードはどう除去しましたか?」

「PRを探してみて。ADR-031にPR番号が書いてある」

「あ、ありました。完璧に理解できました」

1週間でナオは最初のPRを出した。2週間目には本番コードに変更をマージした。通常3ヶ月かかっていたキャッチアップが6週間になった。

「ADRがオンボーディングの資料として機能している」とシンジは確信した。

瞬間2: インシデントの教訓が残る

6ヶ月前、本番でRedisの接続数が上限に達するインシデントが起きた(INC-042)。

原因は2つ。Redis Clusterへのコネクション管理の設定ミスと、特定のキャッシュキーが想定より10倍多く生成されていたことだ。

インシデント対応後、チームはpost-mortemを書くだけでなく、ADRを更新した。

# ADR-029 更新: キャッシュTTLポリシーとコネクション上限設定
# (2024年3月15日インシデントINC-042を受けた更新)
 
## Status
Accepted(インシデント対応で更新)
 
## Context(追記)
2024年3月10日のインシデント(INC-042)でRedisコネクション数が
上限(1000)に達し、本番障害が発生した(ダウンタイム: 約15分)。
 
インシデントの根本原因:
1. キャッシュキーの命名規則が不統一で、想定の10倍のキーが生成されていた
   - 原因: ECSのWorkerタスク追加時にキャッシュキーにプロセスIDを含めてしまった
   - キャッシュキー数: 想定5,000 → 実際50,000
 
2. コネクションプールの上限設定がADRで記録されていなかった
   - Workerタスク追加時に既存のコネクション数計算を考慮できなかった
   - Web x10 * pool5 + Worker x15 * pool5 = 125 → 上限1000以内のはずが
     実際のPoolizeが設定値の2倍になっていた(設定ミス)
 
## Decision(追記)
以下のポリシーを追加する:
 
1. キャッシュキー命名規則を強制する
   - 形式: `{model}:{scope}:v{version}:{params_hash}` (プロセスID禁止)
   - バリデーション: Rspecのcustom matcherで自動チェック
 
2. コネクション数の計算式をADRで明記し、変更時はADRを更新する
   - Webタスク: pool_size = 5
   - Workerタスク: pool_size = 2
   - Batchタスク: pool_size = 1
   - 計算式: (Web数 * 5) + (Worker数 * 2) + (Batch数 * 1) <= 700(上限1000の70%)
 
3. コネクション数の上限をCI/CDで自動チェックする
 
## Consequences(追記)
インシデント対応として追加したテスト:
 
```ruby
# spec/system/redis_connection_spec.rb
describe "Redisコネクション数の制限" do
  it "全タスクのpool_sizeの合計が上限の70%以下" do
    # ECS環境変数から最大タスク数を取得(Terraformと連動)
    max_web_tasks    = ENV.fetch("MAX_WEB_TASKS", "20").to_i
    max_worker_tasks = ENV.fetch("MAX_WORKER_TASKS", "10").to_i
    max_batch_tasks  = ENV.fetch("MAX_BATCH_TASKS", "5").to_i
 
    web_connections    = 5 * max_web_tasks
    worker_connections = 2 * max_worker_tasks
    batch_connections  = 1 * max_batch_tasks
    total = web_connections + worker_connections + batch_connections
 
    expect(total).to be <= 700,
      "コネクション数の合計(#{total})が上限(700)を超えています。" \
      "ECSタスク数またはpool_sizeの見直しが必要です。ADR-029を参照。"
  end
end

インシデントの教訓がADRに刻まれることで、「同じ間違いを繰り返さない」文化が生まれた。

「このADRを見れば、なぜこの制約があるかが分かる」とリョウが言った。「INC-042という辛い経験が、将来のチームへの贈り物になった」

瞬間3: 技術的方針の変更が自然にできた

4ヶ月前、SidekiqからGoodJobに戻すという話が出た。

「え、逆戻り?」とユウキが聞いた。

「状況が変わったんだ」とリョウが答えた。「ADR-031を見て。Sidekiqに変えた理由は『ジョブ量が増えてRedisのボトルネックが出た場合』だった。でも実際はジョブ量が増えなかった。それよりPostgreSQLをすでに使っていてRedisが余分なインフラになってきた」

「それって、ADRの再評価のトリガーに書いてあること?」とユウキ。

「そう。ADR-031のConsequencesに書いてある: 『ジョブ数が1日10万件以下なら GoodJobで十分。その場合はRedisの維持コストを再評価する』」

ユウキはADR-031を開いた。確かに書いてある。

「感情論ゼロで議論できた。ADRが根拠になっているから」とシンジ。「『Sidekiqにしたのに戻すのか』という感情ではなく、『当時の判断条件が変わったから見直す』という論理で話せた」

決定はすぐにできた。ADR-031Superseded by ADR-055 にして、新しいADRを書くだけ。

# ADR-055: SidekiqからGoodJobに戻す
 
## Status
Accepted
 
## Context
ADR-031(2024年1月)でGoodJobからSidekiqに移行した。
移行理由: 「ジョブ量が1日10万件を超えた場合を想定してSidekiqの高スループットが必要」
 
現在の実績(2024年9月時点):
- 1日のジョブ数: 平均8,000件(想定10万件に達していない)
- Sidekiq/Redisのパフォーマンス問題: 発生なし
- Redisの月額コスト: $35(ElastiCache cache.t3.medium)
- ADR-031に書いた再評価トリガー: 「ジョブ数が1日10万件以下なら見直す」に該当
 
ADR-031のConsequencesに書いた条件が成立したため、見直しを実施。
 
## Decision
GoodJobに戻す。Redisコストを削減する。
 
ADR-031の選択肢で「GoodJobを継続しない理由」として挙げた
「ジョブ量増加時のRedisボトルネック」は発生しなかった。
現在の規模(8,000ジョブ/日)ではGoodJobで十分。
PostgreSQLをすでに使っているため、追加インフラが不要。
 
## Consequences
良い影響:
- Redis (ElastiCache)が不要になり月額$35のコスト削減
- インフラ構成がシンプルになる
- Sidekiqのスレッドモデルへの配慮(スレッドセーフ設計)が不要になる
 
悪い影響:
- 将来ジョブ量が増えた場合は再びSidekiqへ移行が必要
- GoodJobのConcurrencyに関する知識がチームから薄れている
  (SidekiqのWorkerとは設定が違う)
 
再評価のトリガー:
- ジョブ数が1日5万件を超えた場合(今度はより早めの対応)
- GoodJobのバージョンアップがRailsと非互換になった場合

ADRが表すもの

シンジは1年分のADRを眺めながら、気づいたことがあった。

ADRの積み重ねは、チームの「判断の歴史」だ。

個々のADRは技術判断の記録。でもそれが58本集まると、別の意味を持ち始める。

Loading diagram...
  • 最初の10本は基礎的な技術選定だった(Rails、PostgreSQL、ECS)
  • 次の10本はスケールへの対応(キャッシュ、インデックス、オートスケーリング)
  • その次はセキュリティと信頼性(認証、インシデント教訓)
  • そして今は、次の段階への準備(マイクロサービス、ML)

チームが「どんな問題に直面してきたか」「どんな価値観で技術を選んできたか」が、ADRに凝縮されている。

シンジはある日、ADRディレクトリを眺めながら思った。

「これは技術の歴史書だ。コードは現在を動かす。ADRは過去の判断を語る。そして未来の判断の根拠になる」

新しいメンバーへの手紙

ナオが入社から8ヶ月。今度は自分が「ADRとは何か」を新入りに説明する番になった。

チームにサーバーサイドエンジニアのタケシが加わった。

ナオはタケシに言った。

「まずdocs/adr/README.mdを読んで」

「このMarkdownファイル群が何?」

「アーキテクチャ意思決定記録。このチームが1年間でどんな技術的判断をしてきたか、なぜそうしたかが書いてある」

「コードより先に読むの?」

「そう。コードは『何をするか』を語る。ADRは『なぜそうするか』を語る。両方読んで初めて、このシステムが分かる」

タケシは半信半疑でADRを読み始めた。3日後、彼はシンジに言った。

「ADR-015、EKS移行を見送った理由が詳しく書いてあって、なぜ今ECSなのかが分かりました。コードを見ただけでは絶対分からなかった。あと、ADR-029にRedisのインシデントが書いてあって、コネクション数の制限にこんな背景があったのかと。勉強になりました」

シンジはうなずいた。インシデントの痛みが、未来への贈り物になった。

INFO

ADRは「現在のチームのため」だけでなく「未来のチームのため」に書く。1年後、3年後のメンバーが「なぜこうなっているのか」を知るための贈り物だ。書くコストは小さく、受け取る価値は大きい。

ADR文化の定着に必要なこと

1年間を振り返り、シンジは「ADR文化の定着に必要なもの」をまとめた。

1. 最初の10本が最も重要

文化は最初の10本で決まる。最初の10本が「良いADR」であれば、後から書く人はそれを手本にする。シニアエンジニアが率先して良いADRを書くことが重要だ。

「自分が書かずに『書いて』とお願いするだけでは文化は生まれない」とシンジは言った。「リーダーが率先して、良い例を示す。これが全て」

2. ハードルを上げすぎない

完璧なADRより、書かれたADRの方が価値がある。最初は5行でいい。議論を経て補完されていく。

「書いた後でADRを充実させることはできる。でも書かれなかったADRは存在しない」

3. コードレビューでADRに言及する

「このPRに関連するADR番号を教えてください」「このADRのConsequencesに書いてあることが起きましたね」という言及を習慣化することで、ADRが「参照されるドキュメント」になる。

「参照されないADRは死ぬ。参照されることで生き続ける」

4. ADRが役立った瞬間を共有する

「ADRがあってよかった」という瞬間をSlackで共有する。これが一番の文化醸成だ。

シンジのチームでは「#adr-wins」というSlackチャンネルを作った。そこでADRが役立った事例を共有する。

(実際の投稿)

リョウ @#adr-wins:
「今日、ナオから『ElastiCacheのクラスター設定を変えていい?』と聞かれて
ADR-024を見てもらったら全部解決した。
『あー、なるほど。INC-042があったから制約があるんですね』って。
ADRがなかったら30分の説明が必要だった。3分で終わった」

ユウキ @#adr-wins:
「ADR-012のlocalStorage禁止の件、フロントのPRレビューで
すぐ指摘できた。理由も説明できた。
『なぜ?』と聞かれてADRのURLを貼るだけでよかった」

5. 四半期レビューを続ける

定期的に「古いADRのアップデート」をすることで、ADRが「生きているドキュメント」であり続ける。

「メンテされないドキュメントは誰にも信用されなくなる。ADRも同じだ」

シンジの振り返り

1年後のスプリントレトロスペクティブで、シンジはチームに言った。

「1年前、入社したとき、コードベースを見て途方に暮れた。なぜそうなっているのか誰も知らなかった」

「今はどうか?」

マイが答えた。「少なくとも、知らないことは『知らない』と分かる。どのADRを読めばいいかが分かる。そしてほとんどのことはADRに書いてある」

リョウが続けた。「設計の議論が速くなった。以前は毎回ゼロから比較してたけど、今は『ADR-015でEKS見送ったけど状況変わった?』という議論ができる。スタート地点が違う」

コウスケが言った。「新しいメンバーへのオンボーディングが楽になった。ADRを読んでもらうだけで、チームの技術観が伝わる。説明する時間が半分以下になった」

ユウキが最後に言った。「自分も判断に参加できるようになった。ADRを読めば、先輩の判断の根拠が見える。同じ視点で考えられる。ジュニアだからって遠慮しなくていいと感じた」

シンジはうなずいた。

「なぜそうなっているのか誰も知らない」から「なぜそうなっているかはADRに書いてある」へ。

それは技術的な問題の解決だけでなく、チームのあり方の変化だった。

あなたのチームで始める ADR

この物語を読んで、「自分のチームでも始めたい」と思ったなら、今日から始められる。

ステップ1: 最初の1本を書く(今日)

今のプロジェクトで「なぜこうなっているのか分からない」ことを1つ選ぶ。知っている人に聞いて、ADRに書く。完璧でなくていい。

# ADR-001: [あなたのチームの最初の判断]
 
## Status
Accepted
 
## Context
[なぜこの判断が必要だったか]
 
## Decision
[何を決めたか]
 
## Consequences
[良い面と悪い面]

ステップ2: PRにADRを含める(来週から)

次のPRに関連するADRを含める。新しい判断ならADRを書く。既存の判断なら参照する。

# ADRを管理するディレクトリを作る
mkdir -p docs/adr
 
# 最初のADRを書く
touch docs/adr/001-first-decision.md
 
# コミットする
git add docs/adr/
git commit -m "docs: ADR-001 プロジェクト最初のアーキテクチャ判断を記録"

ステップ3: チームで共有する(来月)

「ADRがあってよかった」という瞬間を共有する。チームの文化は事例と共に育つ。

ステップ4: 四半期レビューを設ける(3ヶ月後)

「今のADRは正確か」「新しいADRが必要な判断はないか」を定期的に見直す。

コードは「何をするか」、ADRは「なぜそうするか」

Loading diagram...

設計判断の記録は、技術的負債への最も優れた予防策だ。

コードが「今を動かす」のであれば、ADRは「未来を動かせるようにする」。

今日の意思決定が、明日のアーキテクチャになる。

シンジのチームが証明したように、記録する文化があれば、チームは過去から学び、自信を持って未来を作れる。

「なぜそうなっているのか誰も知らない」という状態は、技術的な問題ではない。それは文化の問題だ。ADRはその文化を変える最もシンプルな方法だ。

最初の1本を書くのに、チームの合意も、特別なツールも、長いドキュメントも必要ない。

必要なのは、一人の誰かが「今日の判断を記録しよう」と決めることだけだ。


あなたのチームの最初のADRを書く準備はできましたか?

# 始めるのは今日
mkdir -p docs/adr
cat > docs/adr/001-first-decision.md << 'EOF'
# ADR-001: [最初の重要な技術判断]
 
## Status
Accepted
 
## Context
[なぜこの判断が必要だったか]
 
## Decision
[何を決めたか]
 
## Consequences
[良い面と悪い面]
EOF
 
git add docs/adr/001-first-decision.md
git commit -m "docs: ADR文化を始める第一歩"
echo "ADRの旅が始まりました"

アーキテクチャは意思決定の積み重ねだ。その積み重ねを記録することから、すべてが始まる。


ADR文化の先にあるもの

シンジのチームがADRを始めて1年後、彼は別の変化にも気づいた。

ADRを書く文化は、技術的意思決定の質そのものを上げていた。

「ADRに書く」という前提があることで、意思決定のプロセスが変わった。

ADR前の意思決定:

1. 誰かが「こうしよう」と言う
2. 反論がなければそれに決まる
3. 理由は誰かの頭の中にしかない

ADR後の意思決定:

1. 「ADRを書くとしたら何を書く?」という問いから始まる
2. Context(問題の背景)を言語化する → 問題の本質が明確になる
3. 選択肢を列挙する → 見落としていた選択肢が見つかる
4. 各選択肢のトレードオフを考える → 判断が深くなる
5. Consequencesを考える → 実装前にリスクが見える
6. ADRとして記録する

「ADRを書くプロセス自体が、思考を整理するツールになっている」とリョウが言った。

「ADRを書いているとき、何回か『あ、この選択肢を考えていなかった』と気づいた。書くことで盲点が見える」

これはADRの予期しない副次効果だった。記録するためのツールが、考えるためのツールになっていた。

次の世代へ

シンジはある日、タケシ(最新の新メンバー)からこんな質問を受けた。

「ADRって、いつかなくなるものですか? 将来もっといいやり方が出てくるんじゃないかと思って」

シンジは少し考えた。

「ADRの形式は変わるかもしれない。MarkdownじゃなくてNotionになるかもしれないし、AIが自動で書くようになるかもしれない。でも根本にある問いは変わらない」

「根本の問いって?」

「『なぜそうするか』を記録する。それだけだ。ツールが変わっても、その問いは変わらない。人間がソフトウェアを書き続ける限り、この問いは永遠に重要だ」

タケシはうなずいた。

「分かりました。じゃあ僕が将来テックリードになったとき、ADRじゃない何かで『なぜそうするか』を記録する仕組みを作れれば、それが正解ですね」

シンジは笑った。

「その通り。そして、どんな仕組みにしたか、ADRで記録しておいてくれ」

付録: ADRクイックリファレンス

ADRテンプレート(最小版)

# ADR-XXX: [タイトル]
 
## Status
Accepted
 
## Context
[問題の背景と制約。具体的な数字を含める]
 
## Decision
[何を決めたか。1文で明確に]
 
選ばなかった理由:
- [選択肢A]: [理由]
- [選択肢B]: [理由]
 
## Consequences
良い影響:
- [具体的なメリット]
 
悪い影響:
- [具体的なデメリット・リスク]
 
再評価のトリガー:
- [この判断を見直す条件]

よく書くADRの種類(Rails + AWSプロジェクト)

判断の種類ADRが必要な判断例
データベースDB選定, インデックス戦略, マイグレーション方針
認証・認可認証方式, セッション管理, ロール設計
非同期処理バックグラウンドジョブ, キュー選定
キャッシュキャッシュ戦略, TTL設定, 整合性保証
インフラAWS サービス選定, オートスケーリング
外部サービス決済, メール, ストレージ, 監視ツール
API設計バージョニング, フォーマット, 認証方法
パフォーマンスN+1対策, クエリ最適化の方針

ADRのステータス早見表

ステータス意味次のアクション
Proposed議論中チームでレビュー・フィードバック
Accepted決定済み実装に進む
Rejected却下理由を残して保存
Deprecated非推奨代替案を示す
Superseded by ADR-XXX新ADRに置換新ADRへのリンクを追加