mybook

ADR とチーム — 意思決定プロセスの民主化

変化の兆候

ADR導入から半年。シンジは気づいていた。

変化は数字に現れていた。

指標ADR前ADR後6ヶ月
「なぜこうなってるか」に答えられないケース週に5〜10件週に0〜1件
新メンバーのオンボーディング期間3ヶ月6週間
同じ技術比較の議論(繰り返し)月に3〜4回ほぼゼロ
アーキテクチャに関するPRコメント多い(毎回説明が必要)少ない(ADRに誘導)
インシデントでの根本原因特定時間平均90分平均30分

しかし、もっと大きな変化は数字では見えないところにあった。

チームのジュニアエンジニア、ユウキが言った。

「先週、ライブラリの選定でチームに意見を言えたんです。ADRを読んで、過去の判断の理由が分かったから。前なら黙って先輩の判断に従うだけでした」

シンジは「それだ」と思った。ADRが生んでいたのは技術的な記録だけではなかった。

ADRが生む心理的安全性

ADRは単なるドキュメントではなかった。「誰でも判断に参加できる」という文化を作っていた

理由はシンプルだ。

過去の判断の根拠が見える → 「なんとなくこうしてきた」への挑戦が可能になる。

判断の記録がある → 「あなたのせいで失敗した」ではなく「当時の状況でベストだった、でも今は条件が変わった」と振り返れる。

プロセスが透明 → シニアだけが知っている暗黙の情報がなくなる。

Loading diagram...

ユウキが「意見を言えた」のは、彼が成長したからではない(それもあるが)。ADRが「どんな根拠でどんな判断をするか」の手本を示してくれていたからだ。

「ADR-015を読んだんです。ECS vs EKSの議論。シンジさんたちがどう比較して、どう判断したか。そのプロセスを見て、自分も同じように考えられると思った」

INFO

ADRの最大の副次効果は、技術的意思決定の「民主化」だ。シニアの暗黙知を文書化することで、誰でもアーキテクチャ議論に参加できる土台ができる。知識の非対称性がなくなることで、チームの集合知が最大化される。

RFC(Request for Comments)との違い

「ADRだけで全ての判断を記録しようとするとうまくいかない」とシンジはチームに話した。

「大きな設計変更には、ADRより前に『RFC』が必要なこともある」

ADRとRFCとDesign Docの使い分け

ドキュメント目的タイミング長さ主な読者
RFC設計を議論する判断前長め意見を持つ全員
ADR判断を記録する判断後中程度将来のチームメンバー
Design Doc実装を説明する実装前後長め実装する人

「順番で考えると: RFC で議論 → ADR に記録 → Design Doc で実装詳細、だ」

RFCの書き方

RFCの例: AI推薦エンジンのマイクロサービス化の提案

# RFC-003: AI推薦エンジンをマイクロサービスとして分離する
 
## メタデータ
- **著者**: シンジ・タカハシ
- **状態**: 議論中
- **フィードバック期限**: 2024年9月15日
- **関連ADR**: ADR-015(ECS vs EKS)
- **Slack**: #tech-architecture
 
---
 
## 背景
 
現在、商品推薦ロジックはRailsのメインアプリに含まれている:
```ruby
# app/services/recommendation_service.rb(現状)
class RecommendationService
  def recommend_for(user)
    # 単純なルールベース推薦(協調フィルタリングなし)
    user.viewed_products
        .map(&:similar_products)
        .flatten
        .uniq
        .first(10)
  end
end

プロダクト側から「機械学習ベースの推薦」の要求が来ている。 現在のRailsアプリにPythonのML処理を組み込むのは困難。

問題提起

  1. RailsアプリにPythonを組み込むと、デプロイの複雑さが増す
  2. ML処理のスケーリングがWebアプリのスケーリングと分離できない
  3. ML研究チームがRailsを触れない(Pythonしか使えない)

提案

案A: 推薦エンジンをGoで書いた独立サービスに分離

  • RailsからHTTP API経由でコール
  • 推薦ロジックをGoで実装(シンプルなルールベース + 将来ML)

メリット:

  • 独立デプロイ・独立スケール
  • MLエンジンへの将来拡張が容易

デメリット:

  • サービス間通信のレイテンシ追加(約5ms)
  • インフラの複雑さ増加

試作実装(Go):

// recommendation-service/main.go
package main
 
import (
    "encoding/json"
    "net/http"
    "strconv"
 
    "github.com/gin-gonic/gin"
)
 
type RecommendationRequest struct {
    UserID     int   `json:"user_id"`
    ViewedIDs  []int `json:"viewed_product_ids"`
    MaxResults int   `json:"max_results"`
}
 
type RecommendationResponse struct {
    ProductIDs  []int   `json:"product_ids"`
    Scores      []float64 `json:"scores"`
    ModelVersion string  `json:"model_version"`
}
 
func main() {
    r := gin.Default()
 
    r.POST("/recommendations", func(c *gin.Context) {
        var req RecommendationRequest
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
 
        // 推薦ロジック(現在はルールベース、将来はML)
        recommended := getRecommendations(req.UserID, req.ViewedIDs, req.MaxResults)
 
        c.JSON(http.StatusOK, RecommendationResponse{
            ProductIDs:   recommended,
            Scores:       calculateScores(recommended, req.UserID),
            ModelVersion: "v1.0-rule-based",
        })
    })
 
    r.Run(":8080")
}

案B: RailsアプリにPythonサイドカーを追加

  • ECSタスクにPythonコンテナを追加
  • 同一タスク内でHTTP通信

メリット: サービス間ネットワーク通信がない デメリット: デプロイ単位が複雑

案C: 現状維持 + 外部MLサービス利用(AWS Personalize等)

メリット: 実装コスト低 デメリット: 月額コスト高(推定$500〜$2000/月)、カスタマイズ制限

判断に必要な情報

  1. ML研究チームがGoを書けるか? 書けなければ案Bかもしれない
  2. 推薦APIのレイテンシ要件は? 5msの追加は許容できるか?
  3. AWS Personalizeのコスト感: 試算したチームはいるか?

フィードバック期限

2024年9月15日


コメント

リョウ: 案Aが技術的にはクリーン。GoはシンプルなAPIサービスに向いてる。 レイテンシの5msは許容できる。

マイ: ML研究チームに確認したら「Pythonがいい」と言ってた。 Goだと採用が難しいかも。案Bか案Cを検討すべきでは?

コウスケ: AWS Personalizeを試算した。MAU 80,000で月$800程度。 初期実装コストを考えると悪くないかもしれない。

シンジ: ML研究チームの要望が重要な変数だった。 Pythonがマストなら案Bか案Cに絞る。来週MLチームと打ち合わせを入れる。


RFCで議論が終わったら、その結果をADRに書く。

```markdown
# ADR-033: AI推薦エンジンはPythonマイクロサービスとしてECSに追加する

## Status
Accepted

## Context
RFC-003でAI推薦エンジンの実装方式を議論した(2024年9月1〜15日)。
詳細はRFC-003を参照。

議論の結果、判断に決定的だった要因:
- ML研究チームがPython以外を使えない(Goは不可)
- AWS Personalizeのコスト($800/月)は現フェーズでは高い
- サイドカーパターン(案B)はデプロイ複雑性が課題

## Decision
Pythonで書いたFastAPI製のマイクロサービスをECSの別サービスとして追加する。

RailsメインアプリからはHTTP APIで呼び出す(レイテンシ: 約5ms想定)。

(以下省略)

Design Doc との連携

Design Docは「実装の詳細を説明する」ドキュメントで、ADRより長く、より実装寄りだ。

ADRが「なぜ」を語るなら、Design Docは「どうやって」を語る。

# Design Doc: AI推薦エンジンの実装設計
 
**ステータス**: 実装中
**ADR参照**: ADR-033 (docs/adr/033-recommendation-service.md)
**RFC参照**: RFC-003
 
## 概要
 
ADR-033で決定したPython FastAPIによる推薦サービスの実装設計。
このドキュメントはADRの「なぜ」に続く「どうやって」を説明する。
 
## APIインターフェース
 
### エンドポイント
POST /api/v1/recommendations
 
### リクエスト
```json
{
  "user_id": 12345,
  "viewed_product_ids": [100, 200, 300],
  "max_results": 10,
  "context": "homepage"
}

レスポンス

{
  "product_ids": [400, 500, 600],
  "scores": [0.95, 0.87, 0.82],
  "model_version": "v1.0",
  "processing_time_ms": 12
}

Pythonでの実装

# recommendation_service/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import numpy as np
import logging
 
app = FastAPI(title="Recommendation Service", version="1.0.0")
logger = logging.getLogger(__name__)
 
class RecommendationRequest(BaseModel):
    user_id: int
    viewed_product_ids: List[int]
    max_results: int = 10
    context: Optional[str] = "default"
 
class RecommendationResponse(BaseModel):
    product_ids: List[int]
    scores: List[float]
    model_version: str
    processing_time_ms: float
 
@app.post("/api/v1/recommendations", response_model=RecommendationResponse)
async def get_recommendations(request: RecommendationRequest):
    import time
    start = time.time()
 
    try:
        product_ids, scores = recommender.recommend(
            user_id=request.user_id,
            viewed_ids=request.viewed_product_ids,
            n=request.max_results
        )
 
        elapsed = (time.time() - start) * 1000
 
        return RecommendationResponse(
            product_ids=product_ids,
            scores=scores,
            model_version=recommender.model_version,
            processing_time_ms=round(elapsed, 2)
        )
    except Exception as e:
        logger.error(f"Recommendation error for user {request.user_id}: {e}")
        raise HTTPException(status_code=500, detail=str(e))
 
@app.get("/health")
async def health():
    return {"status": "ok", "model_loaded": recommender is not None}

Railsからの呼び出し

# app/services/recommendation_client.rb
#
# AI推薦サービスへのHTTPクライアント
# 詳細: ADR-033 docs/adr/033-recommendation-service.md
#
class RecommendationClient
  BASE_URL = ENV.fetch("RECOMMENDATION_SERVICE_URL")
  TIMEOUT = 0.1  # 100ms タイムアウト(SLO要件)
 
  def self.recommend_for(user, max_results: 10)
    response = connection.post("/api/v1/recommendations") do |req|
      req.body = {
        user_id: user.id,
        viewed_product_ids: user.recently_viewed_product_ids,
        max_results: max_results
      }.to_json
      req.headers["Content-Type"] = "application/json"
    end
 
    JSON.parse(response.body)["product_ids"]
  rescue Faraday::TimeoutError
    # タイムアウト時はフォールバック(ルールベース推薦)
    Rails.logger.warn("Recommendation service timeout. Falling back to rule-based.")
    user.popular_products(limit: max_results).map(&:id)
  rescue => e
    Rails.logger.error("Recommendation service error: #{e.message}")
    []
  end
 
  private
 
  def self.connection
    @connection ||= Faraday.new(url: BASE_URL) do |conn|
      conn.options.timeout = TIMEOUT
      conn.adapter Faraday.default_adapter
    end
  end
end

フォールバック戦略(ADR-033のConsequencesから)

ADR-033のConsequencesに「サービス障害時のフォールバック設計が必要」と書いた。 その実装詳細がここに書いてある。

(以下省略)


「ADRとDesign Docが分かれているから、それぞれの目的が明確だ」とマイが言った。「ADRは将来も読む。Design Docは実装フェーズに読む」

## ADRを「書かされる」から「書きたい」へ

シンジがADRの導入で最も気を使ったのは「文化の醸成」だった。

「最初は『義務でやる』という雰囲気があった」とシンジはチームへのレトロスペクティブで振り返った。

### 最初の抵抗

半年前のSlack:

マイ: 「毎回ADR書くの、正直めんどくさい。コードと二重管理じゃないの?」 コウスケ: 「小さな判断まで書く必要ある? kaminariの選定とか」 リョウ: 「WIkiに書けばよくない? なんでMarkdownファイルで管理するの?」


### シンジの対応

**1. 自分が率先して書く**

シンジは自分が担当した全ての技術判断にADRを書いた。良い例示があることで、「こんな感じで書けばいい」という共通理解が生まれた。

最初の10本のうち、7本はシンジが書いた。残り3本はリョウが書いた。

**2. ADRの恩恵を「見せる」**

ADRのおかげで解決できた、という瞬間を積極的に共有した。

シンジ(Slack): 「今日、コウスケ担当のRedisキャッシュ設定を変えようとしたんだけど ADR-002を読んで『ここの設定は意図的に変えていない』と分かって 危なく壊すところだった。ADRがあってよかった」

(翌日、コウスケが自分のADRに追記を入れた)


**3. ルールではなく文化にする**

「ADRを書かないとPRがマージできない」というルールは最初は作らなかった。(後にCIで軽いチェックは入れた。)代わりに、レビューで自然にADRを活用した。

コードレビューのコメント(シンジから): 「この設定変更、なぜこの値にしたかのコンテキストをADRに残しておくと 将来の自分が助かると思います。5行でも十分です。いかがでしょう?」


「お願い」であって「強制」ではなかった。しかし毎回お願いされるうちに、自然とADRを書くようになった。

**4. ハードルを下げる**

完璧なADRを求めなかった。「5行でもいい。書いてあることが大事」

「5行ADR」の例をチームで共有した:

```markdown
# ADR-021: KaminariをページネーションGemとして使う

## Status
Accepted

## Context
一覧ページのページネーション実装が必要。will_paginateとKaminariが候補。

## Decision
Kaminariを採用。チームの複数プロジェクトで使用実績があり、Rails 7との
互換性問題がwill_paginateで報告されているため。

## Consequences
チームの学習コストがゼロ。将来100万件超のページネーションが必要になった場合は
cursor-based paginationへの移行を検討する。

「これで十分。3ヶ月後の誰かが『なぜKaminari?』と思ったとき、ゼロよりはるかにましな情報がある」

チームのADRレビュー文化

ADRが増えてきたとき、シンジはコードレビューと同じようにADRレビューの文化を作った。

ADRレビューで見るべき視点

コードレビューと違い、ADRレビューで見るべき点は「コードの正しさ」ではない。

ADRレビューのチェックポイント:

1. 「コンテキストが理解できるか?」
   → このPRを見ていない人が読んでも、なぜこの判断が必要だったか分かるか?
   → 数字や具体例が含まれているか?

2. 「トレードオフが正直に書かれているか?」
   → 良いことしか書いていない場合、何を省いているか聞く
   → 「デメリットや注意事項がゼロ」は疑ってかかる

3. 「代替案が書かれているか?」
   → なぜA案を選んで、B案を選ばなかったか?

4. 「将来への言及があるか?」
   → 何があればこの判断を見直すか?

5. 「6ヶ月後の自分が読んで分かるか?」
   → 今の文脈から切り離して読んでみる

実際のADRレビューコメント(GitHub PR上):

# docs/adr/031-postgresql-connection-pooling.md
 
## Context セクションへのコメント
 
リョウ: 「GoodJobを使っていた背景(ADR-003)への参照があると、
なぜ今Sidekiqに変えるのかの対比が分かりやすくなると思います。
"以前はGoodJobを使っていた(ADR-003参照)。以下の理由でSidekiqに移行する"
という書き方はどうでしょう?」
 
---
 
## Consequences セクションへのコメント
 
マイ: 「Sidekiqはスレッドモデルなので、グローバル変数・クラス変数に
スレッドセーフでないコードがある場合に問題が出ます。
このリスクへの言及があると良いと思います。
実際、以前の別プロジェクトでこれにハマったことがあります」
 
---
 
## Decision セクションへのコメント
 
コウスケ: 「選ばなかった理由にGoodJob継続が入っていないのは意図的ですか?
『GoodJobで継続する』という選択肢の評価も書いてあると、
将来のGoodJoBへの回帰判断の際に役立つと思います」

これらのコメントを受けて、ADRの品質が上がった。「ADRのレビューがコードのレビューと同じくらい真剣になってきた」とリョウが言った。

INFO

ADRレビューの質を上げるには、レビュアーが「将来の読者として」ADRを読むことが重要だ。「このPRの文脈を知っている人」として読むと、コンテキストが不足していても気づかない。意図的に「PRを知らない人」の視点で読む。

外部への説明力

ADR導入のもう一つの予期しない恩恵が生まれた。

技術的な意思決定を外部(投資家、採用候補、顧客)に説明しやすくなったのだ。

採用面接でのやり取り:

候補者(シニアエンジニア): 「なぜRuby on Railsを使っているんですか?
今ならGoやTypeScriptを選ぶ会社が多いですが」

シンジ: 「ADR-001に詳細な選定理由を書いています。
公開可能な部分でいうと、2022年当時のチーム構成・採用市場・
優先要件のトレードオフを評価した結果です。
入社後にそのドキュメントを読んでもらえれば背景が分かります。
もし今同じ選定をするなら、違う判断をする部分もあります。
それについても話せますよ」

候補者: 「ADRで意思決定を記録しているチームは珍しいですね。
入社後も議論に参加できそうで魅力を感じました」

採用後のオンボーディングも変わった:

# 新メンバーオンボーディングガイド(抜粋)
 
## Week 1: 読むべきドキュメント
 
### Day 1〜2: ADRを読む
`docs/adr/README.md` から始めて、以下の順番で読んでください:
 
1. ADR-001(なぜRailsか)
2. ADR-008(なぜPostgreSQLか)
3. ADR-012(認証方式)
4. ADR-015(インフラ設計の方針)
 
これを読むことで、このシステムがなぜこのアーキテクチャになっているかが
80%分かります。残り20%はコードを読みながら埋まっていきます。
 
### Day 3: コードを読む
ADRを読んだ後にコードを読むと、設計の意図が分かります。
分からないことはADRを見てください。それでも分からなければ聞いてください。

ナオ(8ヶ月前に入社したシニアエンジニア)が言った。

「ADRを読んでからコードを読む、という順番が本当に効果的だった。普通は逆で、コードを読んでから『なぜこの設計?』と疑問が出る。ADRがあると、最初から『なぜ』を理解した状態でコードを読める」

ADR文化を守る「難しい判断」

ADR文化が定着してくると、別の問題が出てきた。

「ADRを書くのが面倒くさいから、ADR不要と判断する」という行動が起きた。

マイが言った。「コウスケがPRを出してきたんだけど、ADRに書くほどではないと判断して省略してる。でも私から見たら書くべき判断に見える」

シンジは話し合いの場を設けた。

「コウスケ、そのPRのlib/billing_service.rbの変更、なぜADR不要と判断した?」

「課金ロジックの改善で、設計の変更はしてない。バグ修正に近い」

「将来誰かが同じ変更をしようとしたとき、このPRのコンテキストがないと判断できないと思わないか?」

コウスケは考えた。「……確かに、課金ロジックの変更は将来影響が出やすいな。書いたほうがいいか」

判断の基準を改めて共有した:

# ADRが必要かの判断基準(チームガイドライン)
 
**以下のいずれかに当てはまる場合はADRを書く:**
 
- 6ヶ月後の自分が「なぜこうなっているか」と疑問を持ちそう
- 他のエンジニアが変更する可能性があり、文脈がないと間違えそう
- 複数の選択肢を比較して選んだ
- 「触るな」とコメントしたいコードを書いた
- セキュリティ・パフォーマンスに影響する判断
- 外部依存(gem, AWS, 外部API)の選定
 
**ADR不要の判断:**
 
- バグ修正で設計の意図は変わっていない
- 命名・フォーマット等のコードスタイル変更
- テストケースの追加
- 変更の理由がコミットメッセージで十分に説明できる

WARNING

ADR文化が定着すると「ADR不要」という判断も増える。これ自体は健全だ。ただし「ADRを書くのが面倒だから不要と判断する」という本末転倒に注意。判断基準を共有して、チームで一貫性を保つ。

シニアとジュニアの関係の変化

ADR導入から半年後のスプリントレビューで、ユウキが発表した。

「今回のバッチ処理の実装で、ADR-003とADR-031を読んで、Sidekiqのジョブ設計を自分で判断できました。べき等性の確保についてはADR-031のConsequencesにガイドラインが書いてあって、それを参照して実装しました。シンジさんに確認しなくても判断できました」

マイが言った。「半年前なら絶対シンジさんに確認してたやつだ」

シンジはうなずいた。ADRは「先輩が答えを持っている」という構造を変えていた。

「答えはドキュメントにある。それを読んで、自分で判断できる」という構造に。

これは単なる効率化ではない。チームの自律性と成長への投資だ。シニアが答えを一人で持ち続ければ、ジュニアは育たない。ADRはシニアの判断プロセスを「見える化」することで、ジュニアの成長を促す。

「ADRって、教育コストを下げるんじゃなくて、教育の質を上げるんだな」とユウキが言った。

「正確にはそうだ」とシンジ。「ADRは答えじゃなくて、答えの出し方を教える」


最終章では、ADR文化が定着した後の世界と、シンジのチームがどう変わったかを振り返る。そして、あなたのチームでADRを始めるための具体的なスタートラインを示す。