mybook

エピローグ — アーキテクチャは進化する

1年後

βリリースから1年が経った。カオリのECプラットフォームは、目標の10万ユーザーを大きく超え、35万ユーザーに成長していた。チームも6人から18人になった。

「カオリさん、そろそろ在庫管理をマイクロサービスに分けませんか?デプロイのたびに全機能が止まるのが辛いです」

テックリードになったユウが言った。1年前に「マイクロサービスにしよう」と言っていた彼が、今では「適切なタイミング」を理解して議論している。

「ようやくその時が来たね。でもどこから始めるか、慎重に考えよう」

INFO

最良のアーキテクチャは、最初から完璧なものを設計することではなく、現在のニーズに応えながら、将来の変化に対応できる余地を残すことです。そして、適切なタイミングで進化させることです。

移行の原則

1. 境界は実績から学ぶ

# 1年間で明確になったモジュール境界
# engines/inventory/  → 独立したデプロイが必要(高頻度変更・高負荷)
# engines/ordering/   → 安定(変更が少ない)
# engines/catalog/    → 中程度(週1-2回変更)
# engines/users/      → 安定(変更が少ない)
# engines/analytics/  → 独立が望ましい(重いバッチ処理)
 
# 1年動かしてみてわかったこと:
# - 在庫管理はブラックフライデーで異常負荷
# - 注文処理はほぼ一定の負荷
# - カタログは検索最適化のためRedis依存が強い

「最初から予想できていたことはない。1年間のデータが、正しい分割点を教えてくれた」

2. ストラングラーパターンで段階移行

Loading diagram...
# フェーズ2: モジュール境界を厳格化(現在)
# config/initializers/module_boundaries.rb
 
if Rails.env.development?
  # 直接的な境界越えを検出するルール
  ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
    event = ActiveSupport::Notifications::Event.new(*args)
    
    # Inventoryモジュールのテーブルに、Ordering以外からアクセスしていたら警告
    if event.payload[:sql].include?('inventory_') && 
       !caller.any? { |c| c.include?('engines/inventory') || c.include?('engines/ordering') }
      Rails.logger.warn "モジュール境界違反: inventory への不正アクセス\n#{caller.first(5).join("\n")}"
    end
  end
end
 
# フェーズ3への準備: 在庫モジュールのAPIを整備
module Inventory
  class Client
    # 将来的にHTTP APIに差し替えられるように
    def self.check_availability(product_id:, quantity:)
      # 今はEngine内のコードを呼ぶ
      Inventory::AvailabilityChecker.new.call(
        product_id: product_id,
        quantity: quantity
      )
    end
    
    def self.reserve(product_id:, quantity:, order_id:)
      Inventory::Reserver.new.call(
        product_id: product_id,
        quantity: quantity,
        order_id: order_id
      )
    end
  end
end

3. サービス抽出の具体的手順

在庫サービス抽出の計画(6週間):
 
Week 1-2: 準備
  - Inventory::Client の全呼び出し箇所を確認
  - インターフェースを確定(API仕様書を書く)
  - データベースの分離方針を決める
  
Week 3-4: 新サービス開発
  - Rails API として inventory-service を作成
  - Inventory エンジンのコードを移植
  - テストを新サービスに移行
  
Week 5: シャドウモード
  - モノリスと新サービスに両方リクエストを送る
  - 結果を比較して一致を確認
  - 本番ではモノリス側の結果を使用
  
Week 6: カットオーバー
  - Inventory::Client をHTTP APIに差し替え
  - モノリスの在庫テーブルを廃止
  - モニタリングを強化

技術的負債の管理

# 技術的負債のトラッキング
# 「意図的な負債」と「偶発的な負債」を区別する
 
# 意図的な負債の例:ドキュメントに記録する
# ADR-015: OrdersControllerのバリデーション重複を許容する
# 理由: 今週のリリースに間に合わせるため
# 返済計画: 次のスプリントで共通バリデーターに抽出する
# 見積もりコスト: 半日
 
# 偶発的な負債の例:発見次第修正する
# - テストがないコードパス
# - コピペされたビジネスロジック
# - 不明確な変数名

負債の可視化

# lib/tasks/tech_debt.rake
namespace :tech_debt do
  desc '技術的負債のレポートを生成'
  task report: :environment do
    puts "=== 技術的負債レポート ==="
    
    # テストカバレッジ
    coverage = `bundle exec rspec --format progress 2>&1 | tail -1`
    puts "テストカバレッジ: #{coverage}"
    
    # 循環複雑度の高いメソッド
    puts "\n複雑なメソッド(flogスコア上位10):"
    `bundle exec flog --all app/**/*.rb | head -20`.lines.each do |line|
      puts "  #{line}"
    end
    
    # 長すぎるクラス
    puts "\n長すぎるクラス(200行以上):"
    Dir['app/**/*.rb'].each do |file|
      lines = File.readlines(file).length
      puts "  #{file}: #{lines}行" if lines > 200
    end
  end
end

アーキテクチャの継続的改善

フィットネスファンクション

アーキテクチャの品質を自動的にチェックする「フィットネスファンクション」。

# spec/architecture/module_boundaries_spec.rb
RSpec.describe 'モジュール境界の遵守' do
  it 'Inventoryモジュールに直接アクセスしていない(Orderingを除く)' do
    # ordering以外のコードがinventoryのクラスに直接アクセスしていないことを確認
    inventory_references = Dir['app/**/*.rb', 'engines/**/*.rb'].filter_map do |file|
      next if file.include?('engines/inventory/')
      next if file.include?('engines/ordering/')  # Orderingは直接アクセス許可
      next if file.include?('spec/')
      
      content = File.read(file)
      file if content.match?(/Inventory::[A-Z]/)
    end
    
    expect(inventory_references).to be_empty,
      "以下のファイルがInventoryモジュールに不正アクセス:\n#{inventory_references.join("\n")}"
  end
  
  it 'Controller層にビジネスロジックがない' do
    thick_controllers = Dir['app/controllers/**/*.rb'].filter_map do |file|
      content = File.read(file)
      # ActiveRecord の複雑なクエリがコントローラにある
      file if content.scan(/\.where\(|\.joins\(|\.includes\(/).length > 3
    end
    
    expect(thick_controllers).to be_empty,
      "Fatなコントローラを検出:\n#{thick_controllers.join("\n")}"
  end
end

Architecture Decision Review

「ADRは書いたら終わりじゃない」とカオリはチームに言った。

# ADR-001 振り返り(6ヶ月後)
 
## 結果
モジュラーモノリスの選択は正しかった。
 
## 良かったこと
- 3ヶ月でのβリリースを達成できた
- チーム全員がコードベースを把握できている
- 単一デプロイによりデプロイ頻度が週3-4回を実現
 
## 課題
- Inventoryモジュールがブラックフライデーで負荷問題
- 18名になり、デプロイの衝突が月2-3回発生
- 管理画面のクエリが遅くなってきた(CQRS導入検討)
 
## 更新
見直しのトリガー「月間ユーザー35万・チーム18名」に達した。
在庫管理のサービス分割プロジェクトを開始する(ADR-016参照)。

AWSインフラの進化

Loading diagram...
# 1年間のインフラ進化
 
初期(3ヶ月前):
  - ALB → ECS(Rails) → RDS → ElastiCache
  - Lambda: メール・バッチのみ
  - コスト: 月約8万円
 
現在(本番):
  - ALB → ECS(Rails, 3タスク) → RDS Multi-AZ → ElastiCache Cluster
  - EventBridge + 5つのLambda
  - CloudFront(静的アセット)
  - コスト: 月約35万円
 
計画(6ヶ月後):
  - 在庫サービスを独立(ECS別クラスター)
  - 読み取りレプリカ(ダッシュボード用)
  - SQS + Lambda(在庫更新の非同期化)
  - コスト見込み: 月約55万円(スケールに見合うと判断)

次のアーキテクトへ

カオリは1年間の学びをノートにまとめた。

ソフトウェアアーキテクチャを1年間やってみて学んだこと:

1. アーキテクチャは「今の問題」を解くもの
   明日の問題のために複雑な設計を導入しても、
   今日のチームが理解できなければ意味がない。

2. シンプルさは最も難しい
   シンプルな設計を保つには、不要な複雑さを
   「追加しない」という継続的な意思決定が必要。

3. 記録することが最重要
   なぜその設計にしたかを書いていないと、
   6ヶ月後には誰も思い出せない。
   ADRは設計そのものより価値があることがある。

4. チームが理解できる設計だけが生き残る
   10人のチームで1人しか理解できない設計は、
   実質的に機能しない設計だ。

5. 移行は常に可能だ
   「最初からマイクロサービスにしておくべきだった」
   という後悔は無意味。今から正しく分割すればいい。
   良い設計は、移行を可能にする境界を持っている。

6. 技術に惚れるな、問題に惚れろ
   クリーンアーキテクチャが好きだから適用するのではなく、
   テスタビリティの問題を解決するために選ぶ。
   ツールは目的のための手段だ。

INFO

あなたが今直面しているアーキテクチャの問題は、この本で学んだパターンのどれかで解決できるかもしれません。ただし、常に「なぜこのパターンが生まれたのか」「どんな問題を解決するのか」を問い続けてください。パターンの名前を知ることではなく、問題を解決することが目的です。

旅の終わりと始まり

この1年、カオリは多くのアーキテクチャパターンを学び、実装し、失敗し、改善した。

モノリスから始まり、レイヤード、クリーン、ヘキサゴナル、CQRS、イベントソーシング、SOA、サーバーレス、イベント駆動—それぞれに存在する理由があり、輝く文脈があった。

しかし、最終的に学んだ最も重要なことは、パターンの名前ではなかった。

それは「なぜ」を問い続けることだった。

「なぜこの設計が問題を解決するのか?」「なぜ今の文脈でこれが最適なのか?」「なぜこの境界はここにあるのか?」

この問いを持ち続けるエンジニアだけが、本当の意味でのアーキテクトになれる。


カオリは、次のジュニアエンジニアに同じ質問を投げかけた。「アーキテクチャ設計書、真っ白なドキュメントを前にして、何から始める?」

それがあなたへの問いでもある。


この本を読んでくれたあなたへ

全13章の旅、お疲れ様でした。カオリが学んだように、アーキテクチャは「決める」ものではなく「育てる」ものです。

コードを書くたびに、設計を見直すたびに、この本に戻ってきてください。同じ章でも、あなたの経験が増えるたびに、違う視点から読めるようになるはずです。

良いコードを。良い設計を。そして、良い問いを。