mybook

ADR の運用 — Git での ADR 管理

3ヶ月後の悩み

ADRを始めて3ヶ月。チームには30本近くのADRが溜まってきた。

コウスケがSlackに書いた。

「Redisのキャッシュ設定を変えようとしてるんだけど、過去のADRにキャッシュ関係のが何本かある。どれが最新か分からなくなってきた……」

シンジはADRのディレクトリを見た。確かに混乱している。

docs/adr/
├── 002-redis-cache.md
├── 008-redis-cluster.md
├── 017-cache-invalidation-strategy.md
├── 024-redis-upgrade.md
├── 029-cache-ttl-policy.md

「Redisだけで5本ある。それぞれの関係性が分からない。ADR-002とADR-024はどっちが最新?」

「ADR-024はADR-002のクラスター化で、ADR-008はADR-002のアーキテクチャの変更で……」とリョウが説明しようとして止まった。「確かにパッと見て分からない」

「インデックスと関連付けの仕組みが必要だ。ADRが溜まってきたら、管理の自動化も考えないといけない」

adr-toolsの導入

シンジはまず、ADRの管理を自動化するCLIツール「adr-tools」を導入した。

# macOSでのインストール
brew install adr-tools
 
# Linuxの場合(GitHub Releasesから)
curl -Lo adr-tools https://github.com/npryce/adr-tools/releases/latest/download/adr-tools
chmod +x adr-tools
sudo mv adr-tools /usr/local/bin/
 
# バージョン確認
adr --version
 
# プロジェクトの初期化
cd my-rails-app
adr init docs/adr
# → docs/adr/ ディレクトリが作成され、0001-record-architecture-decisions.md が生成される

初期化で生成されるADR-0001(adr-toolsの規約):

# 1. Record architecture decisions
 
Date: 2024-01-15
 
## Status
 
Accepted
 
## Context
 
We need to record the architectural decisions made on this project.
 
## Decision
 
We will use Architecture Decision Records, as described by Michael Nygard in this article:
http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions
 
## Consequences
 
See Michael Nygard's article, linked above.

新しいADRを作成:

# 新しいADRを作成(連番は自動)
adr new "PostgreSQLのconnection poolingにPgBouncerを採用する"
# → docs/adr/031-postgresqlのconnection-poolingにpgbouncerを採用する.md が作成される
 
# 既存ADRをsupersede(上書き)する新しいADRを作成
# -s オプションで上書くADRの番号を指定
adr new -s 2 "Redisクラスター構成への移行とコネクション設定更新"
# → ADR-002のStatusが自動的に "Superseded by ADR-031" に変更される
# → 新しいADR-031が作成される
 
# リンクを追加(相互参照)
adr link 031 "Requires" 008 "Is required by"
# → ADR-031とADR-008に相互参照が追加される

adr-toolsが生成するテンプレート:

# 31. PostgreSQLのconnection poolingにPgBouncerを採用する
 
Date: 2024-07-15
 
## Status
 
Accepted
 
## Context
 
[コンテキストを書く]
 
## Decision
 
[決定を書く]
 
## Consequences
 
[結果を書く]

INFO

adr-toolsはADRの連番管理、Supersede(上書き)の自動処理、インデックスファイルの生成を自動化してくれる。手動管理ではミスが起きやすい「どのADRが有効か」を確実に追跡できる。

インデックスファイルの整備

30本近くになったADRを整理するために、インデックスファイルを作った。

# adr-toolsでインデックス(目次)を自動生成
adr generate toc > docs/adr/README.md

生成されたインデックス(自動):

# Architecture Decision Records
 
* [1. Record architecture decisions](0001-record-architecture-decisions.md)
* [2. RedisをキャッシュレイヤーとしてElastiCacheで運用する](002-redis-cache.md)
  * Superseded by [24. Redisクラスターへのアップグレード](024-redis-upgrade.md)
* [8. データベースをMySQL 8.0からPostgreSQL 14に移行する](008-postgresql-migration.md)
* [12. 認証方式をJWT + Refresh Tokenに移行する](012-jwt-refresh-token-auth.md)
...

さらに、シンジはカテゴリ別の整理も追加した(手動で編集):

# ADR Index
 
## はじめに
このドキュメントはADR(アーキテクチャ意思決定記録)の索引です。
新しいADRを追加した後は `adr generate toc > docs/adr/README.md` を実行してください。
 
## カテゴリ別索引
 
### インフラ・クラウド
| ADR | タイトル | Status |
|-----|---------|--------|
| [ADR-006](006-ecs-fargate.md) | ECS Fargateの採用 | Accepted |
| [ADR-015](015-ecs-vs-eks.md) | EKS移行の見送り | Accepted |
| [ADR-024](024-redis-cluster.md) | Redisクラスターへのアップグレード | Accepted |
 
### データベース
| ADR | タイトル | Status |
|-----|---------|--------|
| [ADR-002](002-redis-cache.md) | RedisキャッシュにElastiCacheを採用 | Superseded by 024 |
| [ADR-008](008-postgresql-migration.md) | MySQLからPostgreSQLへの移行 | Accepted |
| [ADR-011](011-rds-read-replica.md) | RDSリードレプリカの追加 | Accepted |
 
### 認証・セキュリティ
| ADR | タイトル | Status |
|-----|---------|--------|
| [ADR-004](004-devise-authentication.md) | Deviseによる認証 | Superseded by 012 |
| [ADR-012](012-jwt-auth.md) | JWT + Refresh Token認証方式 | Accepted |
 
### アプリケーション・フレームワーク
| ADR | タイトル | Status |
|-----|---------|--------|
| [ADR-001](001-rails-framework.md) | Ruby on Railsの採用 | Accepted |
| [ADR-003](003-goodjob.md) | GoodJobによる非同期処理 | Superseded by 031 |
| [ADR-021](021-kaminari.md) | Kaminariのページネーション | Accepted |
| [ADR-031](031-sidekiq-migration.md) | GoodJobからSidekiqへの移行 | Accepted |
 
## 現在有効なADR一覧(Acceptedのみ)
最後に更新: 2024年7月

「これで全体像が掴める」とコウスケが言った。「カテゴリ別に見れるのが特にいい」

PRとADRの連携

次の課題はADRとPRを紐づけることだった。

「新しい設計判断が生まれたとき、PRとADRがセットになるようにしたい」

シンジはGitHubのPRテンプレートを修正した。

<!-- .github/pull_request_template.md -->
 
## 変更内容
 
<!-- 何を変えたか。1〜3文で簡潔に -->
 
## 変更の背景・理由
 
<!-- なぜこの変更が必要だったか -->
 
## 技術的な変更を伴う場合(アーキテクチャ・設計判断)
 
- [ ] このPRに関連するADRを追加した(新しい設計判断)
  - ADR番号: `docs/adr/XXX-*.md`
- [ ] 既存のADRを更新した(設計変更)
  - 更新したADR: `docs/adr/XXX-*.md`
- [ ] 既存のADRを参照するコードコメントを追加した
- [ ] ADRは不要と判断した(理由: )
 
## テスト
 
- [ ] ユニットテストを追加・更新した
- [ ] ステージング環境で動作確認した
- [ ] パフォーマンスへの影響を確認した(変更がある場合)
 
## レビューポイント
 
<!-- レビュアーに特に見てほしい点 -->

実際のPRがこんな形になった:

# PR #342: PostgreSQLのコネクションプール設定をPgBouncerで最適化
 
## 変更内容
PgBouncerをEC2で運用し、コネクションプーリングを最適化する。
RDSへの最大コネクション数を現在の300から100に削減。
 
## 変更の背景・理由
RDSのコネクション数が上限(500)の60%を常時超えており、
スパイク時にコネクション待ちが発生していた(INC-043参照)。
 
## 技術的な変更を伴う場合
- [x] このPRに関連するADRを追加した
  - ADR番号: `docs/adr/031-postgresql-connection-pooling.md`
- [x] 既存のADRを参照するコードコメントを追加した
 
## レビューポイント
- PgBouncerの`pool_mode = transaction`が適切か確認をお願いします
`session`モードとのトレードオフはADR-031を参照)
- ADR-031のConsequencesを読んでからコードレビューしてください

これにより、コードの変更とその判断理由が常にセットで記録されるようになった。

Loading diagram...

GitHub Actionsでのバリデーション

シンジはさらに、ADRのバリデーションをCI/CDに組み込んだ。

# .github/workflows/adr-validation.yml
name: ADR Validation
 
on:
  pull_request:
    paths:
      - 'docs/adr/**'
      - 'app/**'
      - 'config/**'
 
jobs:
  validate-adr:
    name: ADRフォーマット検証
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: ADRの必須セクションをチェック
        run: |
          ERRORS=0
          for file in docs/adr/*.md; do
            # インデックスファイルは除外
            if [[ "$file" == *"README"* ]]; then
              continue
            fi
 
            echo "チェック中: $file"
 
            # 必須セクションの確認
            if ! grep -q "^## Status" "$file"; then
              echo "ERROR: $file - '## Status' セクションがありません"
              ERRORS=$((ERRORS + 1))
            fi
 
            if ! grep -q "^## Context" "$file"; then
              echo "ERROR: $file - '## Context' セクションがありません"
              ERRORS=$((ERRORS + 1))
            fi
 
            if ! grep -q "^## Decision" "$file"; then
              echo "ERROR: $file - '## Decision' セクションがありません"
              ERRORS=$((ERRORS + 1))
            fi
 
            if ! grep -q "^## Consequences" "$file"; then
              echo "ERROR: $file - '## Consequences' セクションがありません"
              ERRORS=$((ERRORS + 1))
            fi
 
            # Statusの値が有効かチェック
            STATUS=$(grep "^## Status" -A1 "$file" | tail -1)
            if [[ "$STATUS" != "Proposed" && \
                  "$STATUS" != "Accepted" && \
                  "$STATUS" != "Deprecated" && \
                  ! "$STATUS" =~ ^Superseded ]]; then
              echo "WARN: $file - Statusの値 '$STATUS' が標準外です"
            fi
          done
 
          if [ $ERRORS -gt 0 ]; then
            echo "ADRバリデーション失敗: ${ERRORS}件のエラー"
            exit 1
          fi
 
          echo "ADRバリデーション成功"
 
      - name: ADRインデックスが最新かチェック
        run: |
          # adr-toolsをインストール
          npm install -g adr-log
 
          # 現在のインデックスを生成して比較
          adr-log -d docs/adr > /tmp/expected_toc.md
 
          if ! diff docs/adr/README.md /tmp/expected_toc.md > /dev/null 2>&1; then
            echo "ERROR: docs/adr/README.mdが最新ではありません"
            echo "以下を実行してください: adr generate toc > docs/adr/README.md"
            diff docs/adr/README.md /tmp/expected_toc.md
            exit 1
          fi
 
          echo "ADRインデックス確認OK"
 
  check-adr-in-pr:
    name: PR内のADR参照チェック
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: 設計判断を含むPRにADRが含まれているかチェック
        run: |
          # 変更されたファイルを取得
          CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
 
          # アーキテクチャに影響するファイルが変更されているか確認
          ARCH_CHANGES=$(echo "$CHANGED_FILES" | grep -E \
            "config/database.yml|config/environments|Gemfile|Dockerfile|terraform/" || true)
 
          if [ -n "$ARCH_CHANGES" ]; then
            echo "アーキテクチャ関連ファイルの変更を検知:"
            echo "$ARCH_CHANGES"
 
            # ADRファイルが含まれているか確認
            ADR_CHANGES=$(echo "$CHANGED_FILES" | grep "docs/adr/" || true)
 
            if [ -z "$ADR_CHANGES" ]; then
              echo "WARNING: アーキテクチャ関連ファイルが変更されていますが"
              echo "ADRが含まれていません。ADRが必要か確認してください。"
              echo ""
              echo "ADRが不要な場合はPRの説明に理由を記載してください。"
              # エラーにはしない(警告のみ)
            fi
          fi

「ADRのフォーマットが正しくないとCIが通らない」とシンジが言った。「書く習慣と、書いたものが正しいことの担保を自動化できる」

WARNING

CIでの強制は「セーフティネット」だ。文化が根付く前の初期段階では有効だが、チームが習慣化したらルールを緩めても良い。最初から厳しくしすぎると「ADRは面倒くさい」という印象を与えてしまう。

ADRの検索性を高める

30本を超えてくると、検索が重要になる。

コマンドラインでの検索

# "redis"に関するADRをファイル名で検索
ls docs/adr/ | grep -i redis
 
# 全ADRのステータスを一覧確認
grep -r "^Accepted\|^Deprecated\|^Superseded" docs/adr/*.md \
  | sed 's|docs/adr/||' | sed 's|.md:| → |'
 
# "Accepted"なADRだけリスト
grep -l "^Accepted$" docs/adr/*.md | grep -v README | sort
 
# Supersededになっているものを見つける
grep -rl "^Superseded" docs/adr/ | grep -v README

内容での検索(ripgrepを使用)

# "ElastiCache"に言及しているADRを検索(ファイル名とコンテキスト付き)
rg "ElastiCache" docs/adr/ --type md
 
# "Sidekiq"について書かれたADRのタイトルを表示
rg "^# \d+\." docs/adr/ --type md | grep -i sidekiq
 
# "再評価のトリガー"セクションを持つADRを検索
rg "再評価のトリガー" docs/adr/ -l
 
# 特定日付以降のADRを検索
rg "Date: 2024-0[7-9]" docs/adr/ --type md
 
# 特定の技術(PostgreSQL)が意思決定に含まれるADRを全て表示
rg "PostgreSQL" docs/adr/ -l | xargs -I{} sh -c 'echo "=== {} ===" && head -3 {}'

GitHubのUIでの検索

# リポジトリ内での高度な検索
 
# GitHubのコード検索(リポジトリ内)
# 例: docs/adr/ ディレクトリでredisを含むファイルを検索
# → path:docs/adr redis
 
# 特定ステータスのADRを検索
# → path:docs/adr "## Status" "Accepted"
 
# 特定月のADRを検索
# → path:docs/adr "Date: 2024-07"

ADRのライフサイクル管理

ADRは「一度書いたら終わり」ではない。技術の変化に合わせてメンテナンスが必要だ。

Loading diagram...

定期的なADRレビュー(四半期)

シンジはチームに「四半期ADRレビュー」を設けた。所要時間: 45分〜1時間。

# 四半期ADRレビューのアジェンダ(テンプレート)
 
**実施日**: XXXX年XX月XX日
**参加者**: チーム全員
**ファシリテーター**: (持ち回り)
 
## 目的
- 現状と乖離したADRを更新する
- Deprecated/Supersededにすべきものを処理する
- 新しいADRが必要な判断がないか確認する
- ADRの文化を振り返る(何が上手くいっているか、改善点は何か)
 
## レビュー項目(事前準備)
- [ ] 前回レビュー以降に追加されたADRを全員が読む
- [ ] 現在"Accepted"のADRが実際に運用中か各担当者が確認
- [ ] 使われなくなった技術・方式がないか確認
 
## 議論項目
1. 各ADRのステータス確認(10分)
   - Superseded/Deprecatedにすべきものはないか?
   - 「再評価のトリガー」に書いた条件が発動していないか?
 
2. 新規ADRの確認(20分)
   - 前回以降の意思決定でADRになっていないものはないか?
   - 過去に「ADR不要」とした判断を見直すものはないか?
 
3. ADR文化の振り返り(10分)
   - 「ADRがあってよかった」瞬間はあったか?
   - 「ADRがなくて困った」瞬間はあったか?
   - ADRの書き方に改善点はあるか?
 
## アクションアイテム
(レビュー後に記録)

実際の四半期レビューで出たアクションアイテム:

## 2024年Q3 レビュー結果
 
### ステータス変更
- ADR-003 (GoodJob): Superseded by ADR-031 ✅
- ADR-004 (Devise): Superseded by ADR-012 ✅
- ADR-002 (Redis基本設定): Superseded by ADR-024 ✅
 
### 新規作成が必要なADR
- GoodJobからSidekiqへの移行(担当: リョウ)→ ADR-031
- RDSリードレプリカ追加(担当: コウスケ)→ ADR-032
 
### ADRなし判断の確認
- Datadogの導入: ADR不要と判断(モニタリングツールは変更頻度低い)
  → 今後Datadogに重大な問題が出た場合は改めてADRを書く
 
### 文化の振り返り
- 良かった点: ナオ(新入り)がADRを読んでオンボーディングが早かった
- 改善点: ADRのレビューがPRで後回しになりがち。ADRを先に書く文化にしたい

Superseded の適切な処理

# GoodJob(ADR-003)をSidekiq(ADR-031)でSupersededにする
adr new -s 3 "GoodJobからSidekiqへの移行"

adr-toolsはADR-003を自動的に更新する:

# ADR-003: GoodJobによる非同期処理(更新後)
 
## Status
Superseded by [ADR-031. GoodJobからSidekiqへの移行](031-goodjob-to-sidekiq-migration.md)
 
(以下は変更なし)
 
## Context
...

WARNING

古いADRを削除してはいけない。SupersededやDeprecatedになっても残す。「なぜGoodJobをやめてSidekiqにしたか」は、GoodJobを選んだ歴史とセットで意味を持つ。削除されたADRは「なぜそうなっているか」を永遠に失わせる。

ADRのコミットメッセージルール

チームはコミットメッセージにもADR番号を含めるルールを設けた。

# 良いコミットメッセージの例
git commit -m "feat: PgBouncerでコネクションプールを最適化 (ADR-031)"
git commit -m "docs: ADR-031 PostgreSQL connection pooling方式を追加"
git commit -m "refactor: JWT有効期限を15分に変更 (ref: ADR-012)"
git commit -m "chore: Sidekiqへの移行完了。GoodJob関連コードを削除 (ADR-031)"
 
# ADR-031に関連するコミット一覧を確認
git log --oneline --grep="ADR-031"
# 出力例:
# a1b2c3d feat: PgBouncerでコネクションプールを最適化 (ADR-031)
# b2c3d4e docs: ADR-031 PostgreSQL connection pooling方式を追加

これにより git log --grep="ADR-012" でADR関連のコミットが追える。

ADRをGitのタグで管理する

チームはADRの大きな節目をGitタグで管理することにした。

# ADRのマイルストーンをタグで記録
git tag -a "adr/v1.0" -m "ADR文化導入: 最初の10本のADRが揃った"
git tag -a "adr/v2.0" -m "ADR運用改善: インデックス・CIバリデーション追加"
git tag -a "adr/infra-2024" -m "2024年インフラADR群: ECS最適化・PgBouncer"

Railsプロジェクトでの最終的なADRディレクトリ

3ヶ月の運用を経て、シンジのチームのADRディレクトリはこうなった。

docs/
└── adr/
    ├── README.md                              # インデックス(一部自動生成)
    ├── 0001-record-architecture-decisions.md  # adr-tools標準
    ├── 001-rails-framework.md                 # Accepted
    ├── 002-redis-cache.md                     # Superseded by 024
    ├── 003-goodjob-background-jobs.md         # Superseded by 031
    ├── 004-devise-authentication.md           # Superseded by 012
    ├── 005-aws-s3-file-storage.md             # Accepted
    ├── 006-ecs-fargate.md                     # Accepted
    ├── 007-stripe-payment.md                  # Accepted
    ├── 008-postgresql-migration.md            # Accepted
    ├── 009-postgis-adoption.md                # Accepted
    ├── 010-sidekiq-redis.md                   # Accepted
    ├── ...(中略)...
    ├── 012-jwt-refresh-token-auth.md          # Accepted
    ├── 015-ecs-vs-eks.md                      # Accepted
    ├── 021-kaminari-pagination.md             # Accepted
    ├── 024-redis-cluster-upgrade.md           # Accepted
    ├── 029-cache-ttl-policy.md                # Accepted(インシデント後更新)
    ├── 031-sidekiq-migration.md               # Accepted
    └── templates/
        └── adr-template.md                    # チーム用テンプレート

チームのテンプレート:

# ADR-XXX: [タイトル]
 
Date: YYYY-MM-DD
Author: [著者名]
Status: Proposed
 
## Status
 
Proposed
 
## Context
 
### 問題の背景
 
[どんな問題が起きていたか。具体的な数字を含める]
 
### 技術的制約
 
- チームスキル:
- コスト制約:
- スケジュール制約:
 
### 検討した選択肢
 
1. [選択肢A]
2. [選択肢B]
3. [選択肢C]
 
## Decision
 
[決定内容を1文で]
 
### 選んだ理由
 
-
 
### 選ばなかった理由
 
**[選択肢B] を選ばなかった理由:**
 
**[選択肢C] を選ばなかった理由:**
 
## Consequences
 
### ポジティブな影響
 
-
 
### ネガティブな影響・リスク
 
-
 
### 実装での注意事項
 
-
 
## 再評価のトリガー
 
- [この判断を見直すべき条件]
 
## 関連ドキュメント
 
- [関連するADR番号・RFC・Design Doc]

コウスケがSlackに書いた。「Redisのキャッシュ設定を変えようとしてたんだけど、ADR-002, 024, 029を読んで状況が完全に理解できた。コードを見るより早かった」

シンジはその投稿をSlackでピン留めした。「これがADRが機能している証拠だ」


次の章では、ADRをチームの文化として定着させる方法と、RFC・Design Docとの使い分け、意思決定プロセスの民主化と心理的安全性への影響を学ぶ。