ソフトウェアアーキテクチャを設計・評価するときの原則、判断基準、実践のポイントをまとめたガイドです。特定の技術スタックには依存しない内容にしています。
目次
- アーキテクチャ設計の目的
- 基本原則
- 品質特性(非機能要件)を起点にする
- 境界とモジュール分割
- 代表的なアーキテクチャスタイルと選び方
- データ設計
- 通信と統合
- 信頼性・回復性
- スケーラビリティとパフォーマンス
- セキュリティ
- 可観測性(Observability)
- デプロイと運用
- 意思決定の記録と進化
- よくあるアンチパターン
- 設計レビュー チェックリスト
1. アーキテクチャ設計の目的
アーキテクチャとは「後から変えにくい重要な決定の集合」です。目的は次の3つに集約されます。
- ビジネス要件と品質要件を満たすこと:機能だけでなく、性能・可用性・保守性などを満たす
- 変更コストを低く保つこと:将来の変更が局所的に済む構造にする
- チームが並行して開発できること:組織構造とシステム構造を整合させる
良いアーキテクチャは「決定を遅らせられる」アーキテクチャである。今決めなくてよいことを、今決めないで済む構造を目指す。
2. 基本原則
2.1 シンプルさを優先する(KISS / YAGNI)
- 現時点で必要な複雑さだけを導入する
- 「将来必要になるかもしれない」拡張ポイントは、根拠がない限り作らない
- 複雑さを導入するときは、それが解決する具体的な問題を説明できるようにする
2.2 関心の分離(Separation of Concerns)
- UI、ビジネスロジック、データアクセス、外部連携を分離する
- 1つのコンポーネントが変更される理由は1つに絞る(単一責任の原則)
2.3 高凝集・疎結合
- 凝集度:一緒に変わるものは一緒に置く
- 結合度:別々に変わるものは依存を最小化し、インターフェースを介して接続する
- 依存の方向を意識する:安定したもの(ドメイン)に向かって依存させる(依存性逆転の原則)
2.4 明示的なインターフェース
- コンポーネント間の契約(API、イベントスキーマ)を明文化する
- 内部実装を隠蔽し、外部に公開する面を小さく保つ
2.5 トレードオフを意識する
- すべての品質特性を最大化することはできない(例:一貫性 vs 可用性、柔軟性 vs 単純さ)
- 何を優先し、何を犠牲にしたかを明示的に記録する
3. 品質特性(非機能要件)を起点にする
アーキテクチャを決めるのは機能要件よりも品質特性です。設計の初期段階で、優先すべき品質特性を特定し、測定可能な形で定義します。
| 品質特性 | 定義の例(測定可能な形) |
|---|---|
| 性能 | 95パーセンタイルのレスポンスタイム 300ms 以内 |
| 可用性 | 月間稼働率 99.9%(許容停止時間 約43分/月) |
| スケーラビリティ | ピーク時 10倍のトラフィックに水平スケールで対応 |
| 保守性 | 新機能の追加が既存モジュール1〜2個の変更で完結する |
| セキュリティ | 個人情報は保存時・通信時ともに暗号化 |
| 回復性 | RTO 1時間、RPO 5分 |
| コスト | 月額インフラ費用を一定額以内に収める |
実践のポイント
- 品質特性は「優先順位付きの上位3〜5個」に絞る
- 品質特性シナリオ(刺激 → 応答 → 測定値)の形で書くと評価しやすい
- 例:「ピーク時に注文APIへ毎秒1,000リクエストが来たとき、95%を500ms以内に処理する」
4. 境界とモジュール分割
4.1 ドメインに沿って分割する
- 技術レイヤー(Controller / Service / Repository)だけでなく、ビジネスの境界で分割する
- ドメイン駆動設計(DDD)の 境界づけられたコンテキスト(Bounded Context) を活用する
- 同じ言葉が異なる意味を持つ場所が境界の目印(例:「顧客」が営業と請求で異なる意味を持つ)
4.2 コンウェイの法則を味方につける
- システムの構造は組織のコミュニケーション構造を反映する
- チーム境界とモジュール境界を一致させると、調整コストが下がる
- 必要に応じて、望むアーキテクチャに合わせてチーム構成を変える(逆コンウェイ戦略)
4.3 境界の健全性を保つ
- モジュール間の依存関係を可視化し、循環依存を禁止する
- 依存ルールを静的解析やアーキテクチャテストで自動検証する
- 他モジュールのデータベースへの直接アクセスを禁止する
5. 代表的なアーキテクチャスタイルと選び方
| スタイル | 向いている状況 | 注意点 |
|---|---|---|
| モジュラーモノリス | 初期フェーズ、小〜中規模チーム、ドメイン理解が途上 | モジュール境界を守る規律が必要 |
| レイヤードアーキテクチャ | 業務アプリ全般、シンプルなCRUD中心 | ドメインロジックが層をまたいで散らばりやすい |
| クリーン / ヘキサゴナル | ドメインロジックが複雑、テスト容易性を重視 | 小規模では過剰になりがち |
| マイクロサービス | 大規模組織、独立デプロイ・独立スケールが必須 | 分散システムの複雑さ(ネットワーク、整合性、運用)が大きい |
| イベント駆動 | 非同期処理、システム間の疎結合、リアルタイム連携 | 処理の流れが追いにくい、結果整合性への対応が必要 |
| サーバーレス | 断続的な負荷、イベント処理、運用負荷を下げたい | コールドスタート、ベンダーロックイン、ローカル開発の難しさ |
選び方の指針
- 迷ったらモジュラーモノリスから始める。境界が安定してから必要な部分だけ分離する
- マイクロサービスは「技術的な理由」より「組織的な理由(チームの独立性)」で採用を判断する
- スタイルは混在してよい。システム全体で1つに統一する必要はない
6. データ設計
6.1 データの所有権を明確にする
- 各データには 唯一の所有者(サービス/モジュール) を定める
- 他のコンポーネントは所有者のAPIやイベントを通じてデータを取得する
- 共有データベースは強い結合を生むため避ける
6.2 整合性モデルを選ぶ
- 強い整合性が必要な範囲(例:残高、在庫の引当)は小さく保つ
- それ以外は 結果整合性 を受け入れ、補償処理(Sagaパターン等)で対応する
- トランザクション境界 = 集約(Aggregate)の境界を基本とする
6.3 ストレージの選択
- 用途に応じて適材適所で選ぶ(RDB、ドキュメントDB、KVS、検索エンジン、時系列DBなど)
- ただし種類を増やすほど運用コストが増える。まずは汎用的なRDBで足りるかを検討する
6.4 スキーマの進化
- 後方互換性を保ったスキーマ変更を基本とする(追加は容易、削除・変更は段階的に)
- Expand → Migrate → Contract の手順で無停止移行を行う
7. 通信と統合
7.1 同期 vs 非同期
| 同期(REST / gRPC 等) | 非同期(メッセージキュー / イベント) | |
|---|---|---|
| 長所 | シンプル、即時に結果が得られる | 疎結合、負荷平準化、障害の局所化 |
| 短所 | 呼び出し先の障害が連鎖する | 処理の追跡が難しい、結果整合性 |
| 向く用途 | 問い合わせ、即時応答が必要な操作 | 通知、バックグラウンド処理、システム間連携 |
7.2 API設計
- APIは公開した時点で契約になる。バージョニング方針を最初に決める
- リソース指向で一貫した命名・エラー形式を採用する
- 冪等性を確保する(冪等キーの導入、PUTの活用など)
- ページング、フィルタリング、レート制限を最初から考慮する
7.3 イベント設計
- イベントは「起きた事実」を過去形で表現する(例:
OrderPlaced) - イベントスキーマを管理し、互換性を保つ
- 少なくとも1回配信(at-least-once)を前提に、受信側を冪等にする
8. 信頼性・回復性
「障害は必ず起きる」を前提に設計する。
- タイムアウト:すべての外部呼び出しにタイムアウトを設定する
- リトライ:指数バックオフ+ジッターで実施し、冪等な操作に限定する
- サーキットブレーカー:障害中の依存先への呼び出しを遮断し、連鎖障害を防ぐ
- バルクヘッド:リソース(スレッド、接続プール)を分離し、障害の影響範囲を限定する
- グレースフルデグラデーション:一部機能が使えなくても主要機能は提供し続ける
- 単一障害点(SPOF)の排除:冗長化、複数アベイラビリティゾーンへの分散
- バックアップと復旧訓練:バックアップは「復元できること」を定期的に確認して初めて意味がある
- カオスエンジニアリング:計画的に障害を注入し、回復性を検証する
9. スケーラビリティとパフォーマンス
- ステートレス化:アプリケーションサーバーに状態を持たせず、水平スケールを容易にする
- キャッシュ:読み取りが多いデータはキャッシュする。ただし無効化戦略を必ずセットで設計する
- 非同期化:重い処理はキューに逃がし、応答時間を短く保つ
- データの分割:読み取りレプリカ、シャーディング、パーティショニング
- ボトルネックの計測:推測ではなく、負荷試験とプロファイリングで特定する
- 早すぎる最適化を避ける:まず正しく動く構造を作り、計測に基づいて最適化する
10. セキュリティ
- セキュリティ・バイ・デザイン:後付けではなく設計段階から組み込む
- 多層防御(Defense in Depth):ネットワーク、アプリケーション、データの各層で防御する
- 最小権限の原則:ユーザー、サービス、インフラそれぞれに必要最小限の権限のみ付与する
- ゼロトラスト:内部ネットワークであっても通信を認証・認可・暗号化する
- 機密情報の管理:シークレットはコードや設定ファイルに埋め込まず、専用の管理基盤を使う
- 入力の検証:すべての外部入力を信頼せず、境界で検証する
- 脅威モデリング:STRIDE等の手法で、設計段階に脅威を洗い出す
- 依存関係の管理:ライブラリの脆弱性を継続的にスキャンし、更新する
- 監査ログ:誰が、いつ、何をしたかを追跡できるようにする
11. 可観測性(Observability)
システムの内部状態を外部から把握できるようにする。
| 要素 | 内容 | ポイント |
|---|---|---|
| ログ | 個別イベントの記録 | 構造化ログ(JSON等)、相関IDの付与、個人情報のマスキング |
| メトリクス | 数値の時系列データ | RED(Rate / Errors / Duration)、USE(Utilization / Saturation / Errors) |
| トレース | リクエストの経路追跡 | 分散トレーシングでサービス間の遅延を可視化 |
実践のポイント
- SLI / SLO を定義し、エラーバジェットで信頼性と開発速度のバランスを取る
- アラートは「ユーザー影響のある症状」に対して設定し、ノイズを減らす
- ダッシュボードはサービスごとに標準化する
12. デプロイと運用
- Infrastructure as Code:インフラ構成をコードで管理し、再現性を確保する
- CI/CD:ビルド、テスト、デプロイを自動化し、小さく頻繁にリリースする
- 段階的リリース:カナリアリリース、ブルーグリーンデプロイ、フィーチャーフラグでリスクを抑える
- ロールバック容易性:すべてのデプロイは即座に戻せるようにする
- 環境の一貫性:開発・検証・本番の差異を最小化する(コンテナの活用など)
- 運用を設計に含める:誰がどう運用するかを設計段階で決め、Runbookを整備する
13. 意思決定の記録と進化
13.1 ADR(Architecture Decision Record)
重要な設計判断は ADR として記録する。
# ADR-XXX: [決定のタイトル]
## ステータス
提案 / 承認 / 廃止 / 置き換え(ADR-YYY)
## コンテキスト
なぜこの決定が必要なのか。前提条件、制約、課題。
## 決定
何を決めたのか。
## 検討した選択肢
- 選択肢A:長所 / 短所
- 選択肢B:長所 / 短所
## 結果
この決定によって何が良くなり、何を受け入れるのか(トレードオフ)。13.2 アーキテクチャの可視化
- C4モデル(Context → Container → Component → Code)で抽象度ごとに図を描く
- 図はコードと同じリポジトリで管理し(Mermaid、PlantUML 等)、陳腐化を防ぐ
13.3 進化的アーキテクチャ
- アーキテクチャは一度決めて終わりではなく、継続的に見直す
- 適応度関数(Fitness Function):守るべき性質(依存ルール、性能閾値など)を自動テストで検証する
- 大きな移行は ストラングラーフィグパターン で段階的に置き換える
14. よくあるアンチパターン
| アンチパターン | 症状 | 対策 |
|---|---|---|
| 分散モノリス | サービスは分かれているが、同時にデプロイしないと動かない | 境界の見直し、同期依存の削減、データ所有権の明確化 |
| ビッグボールオブマッド | 構造がなく、どこを変えても影響が予測できない | モジュール境界の導入、依存ルールの自動検証 |
| 共有データベース | 複数サービスが同じテーブルを直接読み書きする | データ所有者を定め、API/イベント経由に移行 |
| 過剰設計 | 使われない抽象化層や拡張ポイントが大量にある | YAGNIの徹底、必要になってから導入 |
| 黄金のハンマー | どんな課題にも同じ技術・パターンを適用する | 要件と品質特性から選択肢を比較検討する |
| 象牙の塔のアーキテクト | 設計者が実装から離れ、現実と乖離した設計になる | アーキテクトも実装・レビューに参加する |
| ドキュメントなき決定 | 「なぜこうなっているのか」誰も分からない | ADRで判断理由を記録する |
| チャッティなサービス間通信 | 1リクエストで多数のサービス呼び出しが連鎖する | 境界の再設計、データの複製、集約API |
15. 設計レビュー チェックリスト
要件・前提
- 優先すべき品質特性が特定され、測定可能な形で定義されている
- 主要な制約(予算、期限、チームスキル、既存システム)が明確になっている
構造
- モジュール/サービスの境界がドメインに沿っている
- 依存関係の方向が適切で、循環依存がない
- 各データの所有者が明確である
- 採用したスタイルが、規模とチーム構成に見合っている
信頼性・性能
- 単一障害点が特定され、対策されている
- 外部呼び出しにタイムアウト・リトライ・サーキットブレーカーが設定されている
- スケール方針(水平/垂直)とボトルネックの想定がある
- バックアップと復旧手順が定義されている
セキュリティ
- 認証・認可の方式が明確である
- 機密データの暗号化(保存時・通信時)が考慮されている
- シークレット管理の方法が決まっている
- 脅威モデリングを実施している
運用
- ログ・メトリクス・トレースの方針が決まっている
- SLO とアラート方針が定義されている
- デプロイとロールバックの手順が自動化されている
記録・進化
- 重要な判断が ADR として記録されている
- アーキテクチャ図が最新の状態に保たれている
- 守るべきルールが自動テスト(適応度関数)で検証されている
参考文献
- Robert C. Martin『Clean Architecture』
- Eric Evans『エリック・エヴァンスのドメイン駆動設計』
- Martin Kleppmann『データ指向アプリケーションデザイン』
- Mark Richards, Neal Ford『ソフトウェアアーキテクチャの基礎』
- Neal Ford ほか『進化的アーキテクチャ』
- Sam Newman『マイクロサービスアーキテクチャ』
- Google『SRE サイトリライアビリティエンジニアリング』