メインコンテンツまでスキップ

アーキテクチャパターン - MVC からヘキサゴナルまで

このドキュメントの目的

フレームワークが採用している代表的なアーキテクチャパターンを理解し、それぞれの特徴と使い分けを学びます。


目次

  1. MVC パターン
  2. レイヤードアーキテクチャ
  3. ヘキサゴナルアーキテクチャ
  4. クリーンアーキテクチャ
  5. パターンの比較
  6. まとめ

MVC パターン

Model-View-Controller

MVC = UIを持つアプリケーションの構造パターン。1979年にSmalltalkで考案。

┌─────────────────────────────────────────────────────────────┐
│ MVC の基本構造 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ View │ │
│ │ (表示) │ │
│ └─────┬──────┘ │
│ │ │
│ │ ユーザー操作 │
│ ↓ │
│ ┌────────────┐ ┌────────────┐ │
│ │ Model │←──────│ Controller │ │
│ │ (データ) │ 更新 │ (制御) │ │
│ └─────┬──────┘ └────────────┘ │
│ │ │
│ │ 変更通知 │
│ ↓ │
│ ┌────────────┐ │
│ │ View │ │
│ │ (更新) │ │
│ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

各コンポーネントの役割

┌─────────────────────────────────────────────────────────────┐
│ MVCの役割分担 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Model(モデル) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・アプリケーションのデータとビジネスロジック │ │
│ │ ・データの検証、保存、取得 │ │
│ │ ・ViewやControllerを知らない(独立) │ │
│ │ │ │
│ │ 例: User, Order, Product │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ View(ビュー) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・データの表示(UI) │ │
│ │ ・ユーザー入力の受付 │ │
│ │ ・Modelの状態を画面に反映 │ │
│ │ │ │
│ │ 例: HTML, JSP, Thymeleaf, React Component │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Controller(コントローラー) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・ユーザー入力を受け取り、適切な処理を呼び出す │ │
│ │ ・ModelとViewの仲介 │ │
│ │ ・処理の流れを制御 │ │
│ │ │ │
│ │ 例: UserController, OrderController │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Spring MVC での実装例

// ========================================
// Model
// ========================================
@Entity
public class User {
@Id
private Long id;
private String name;
private String email;
// getters, setters
}

// ========================================
// Controller
// ========================================
@Controller
public class UserController {
private final UserService userService;

@GetMapping("/users")
public String listUsers(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users); // Viewにデータを渡す
return "users/list"; // View名を返す
}

@PostMapping("/users")
public String createUser(@ModelAttribute User user) {
userService.save(user);
return "redirect:/users";
}
}

// ========================================
// View (Thymeleaf テンプレート)
// ========================================
// users/list.html
<table>
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
</tr>
</table>

MVC の派生パターン

┌─────────────────────────────────────────────────────────────┐
│ MVC派生パターン │
├─────────────────────────────────────────────────────────────┤
│ │
│ MVP (Model-View-Presenter) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ View ←→ Presenter ←→ Model │ │
│ │ │ │
│ │ ・ViewとModelが直接やり取りしない │ │
│ │ ・Presenterが全てを仲介 │ │
│ │ ・テストしやすい │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ MVVM (Model-View-ViewModel) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ View ←→ ViewModel ←→ Model │ │
│ │ ↑ │ │
│ │ └── データバインディング │ │
│ │ │ │
│ │ ・ViewとViewModelがデータバインディングで連携 │ │
│ │ ・Vue.js, Angular, WPFなどで採用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

レイヤードアーキテクチャ

層構造による分離

レイヤードアーキテクチャ = アプリケーションを水平な層に分割する構造。

┌─────────────────────────────────────────────────────────────┐
│ レイヤードアーキテクチャ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Presentation Layer │ │
│ │ (プレゼンテーション層) │ │
│ │ │ │
│ │ HTTPリクエスト/レスポンス、UIの処理 │ │
│ │ Controller, REST API, View │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ 呼び出し │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Business Layer │ │
│ │ (ビジネス層) │ │
│ │ │ │
│ │ ビジネスロジック、ユースケースの実行 │ │
│ │ Service, Domain Model │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ 呼び出し │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Persistence Layer │ │
│ │ (永続化層) │ │
│ │ │ │
│ │ データベースアクセス、ファイルI/O │ │
│ │ Repository, DAO │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ 呼び出し │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Database │ │
│ │ (データベース) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ルール: 依存は上から下への一方向 │
│ │
└─────────────────────────────────────────────────────────────┘

典型的なSpringアプリケーション構造

src/main/java/com/example/
├── controller/ ← Presentation Layer
│ └── UserController.java
├── service/ ← Business Layer
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
├── repository/ ← Persistence Layer
│ └── UserRepository.java
└── entity/ ← 全層で共有
└── User.java

レイヤードの問題点

┌─────────────────────────────────────────────────────────────┐
│ レイヤードアーキテクチャの問題点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. データベース中心の設計になりがち │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 「まずテーブル設計」→「Entityを作成」→「ロジック」 │ │
│ │ │ │
│ │ → ビジネスロジックがDB構造に引きずられる │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. 上位層が下位層に依存 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Service → Repository → Database │ │
│ │ │ │
│ │ DBを変えるとRepository変更 → Service影響の可能性 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. Entityが全層を貫通 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Controller → Service → Repository │ │
│ │ ↓ ↓ ↓ │ │
│ │ User User User (JPA Entity) │ │
│ │ │ │
│ │ → JPA Entityの変更が全層に波及 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

ヘキサゴナルアーキテクチャ

Ports and Adapters

ヘキサゴナルアーキテクチャ = ビジネスロジックを中心に置き、外部との接続を「ポート」と「アダプター」で抽象化する。

┌─────────────────────────────────────────────────────────────┐
│ ヘキサゴナルアーキテクチャ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 外部(Driving側) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ REST │ │ CLI │ │ Test │ │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ │ │
│ ↓ Port (入力) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ Application Core │ │
│ │ (アプリケーションコア) │ │
│ │ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ Domain Model │ │ │
│ │ │ (ビジネスロジック・ルール) │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ↓ Port (出力) │
│ ┌────────────┼────────────┐ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │Database │ │ External│ │ Email │ │
│ │ Adapter │ │ API │ │ Adapter │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 外部(Driven側) │
│ │
└─────────────────────────────────────────────────────────────┘

ポートとアダプターの関係

┌─────────────────────────────────────────────────────────────┐
│ ポートとアダプター │
├─────────────────────────────────────────────────────────────┤
│ │
│ Port(ポート)= インターフェース │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・アプリケーションコアが定義する「契約」 │ │
│ │ ・外部との境界を明確にする │ │
│ │ ・入力ポート: 外部 → コア(UseCase) │ │
│ │ ・出力ポート: コア → 外部(Repository) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Adapter(アダプター)= 実装 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・ポートの具体的な実装 │ │
│ │ ・技術的な詳細を担当 │ │
│ │ ・Driving Adapter: REST Controller, CLI │ │
│ │ ・Driven Adapter: JPA Repository, HTTP Client │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 例: │ │
│ │ │ │
│ │ Port(インターフェース): │ │
│ │ interface UserRepository { │ │
│ │ User findById(Long id); │ │
│ │ } │ │
│ │ │ │
│ │ Adapter(実装): │ │
│ │ class JpaUserRepository implements UserRepository │ │
│ │ class InMemoryUserRepository implements UserRepository│ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

依存の方向

┌─────────────────────────────────────────────────────────────┐
│ 依存性の方向 │
├─────────────────────────────────────────────────────────────┤
│ │
│ レイヤード(従来): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Controller → Service → Repository → DB │ │
│ │ │ │
│ │ → 全てが外部(DB)に依存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ヘキサゴナル: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Controller JpaRepository │ │
│ │ │ │ │ │
│ │ │ 実装 │ 実装 │ │
│ │ ↓ ↓ │ │
│ │ InputPort ←─── Core ───→ OutputPort │ │
│ │ │ │ │
│ │ └── Domain Model │ │
│ │ │ │
│ │ → 外部がコアに依存(依存性の逆転) │ │
│ │ → コアは外部を知らない │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

idp-server での実装例

┌─────────────────────────────────────────────────────────────┐
│ idp-server のモジュール構成 │
├─────────────────────────────────────────────────────────────┤
│ │
│ idp-server-core(コア) │
│ ├── domain/ ← ドメインモデル │
│ │ ├── User.java │
│ │ └── Token.java │
│ ├── service/ ← ビジネスロジック │
│ │ └── TokenService.java │
│ └── repository/ ← 出力ポート(インターフェース) │
│ └── UserRepository.java │
│ │
│ idp-server-core-adapter(アダプター) │
│ └── repository/ ← ポートの実装 │
│ ├── postgres/ │
│ │ └── PostgresUserRepository.java │
│ └── mysql/ │
│ └── MySQLUserRepository.java │
│ │
│ idp-server-springboot-adapter(アダプター) │
│ └── controller/ ← 入力アダプター │
│ └── TokenController.java │
│ │
│ → コアは実装の詳細(PostgresかMySQLか)を知らない │
│ → アダプターを差し替えるだけでDB変更可能 │
│ │
└─────────────────────────────────────────────────────────────┘

メリット

┌─────────────────────────────────────────────────────────────┐
│ ヘキサゴナルアーキテクチャのメリット │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. テスト容易性 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DBなしでコアのテストが可能 │ │
│ │ InMemoryRepositoryに差し替えるだけ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. 技術の入れ替え │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PostgreSQL → MySQL: アダプター差し替えのみ │ │
│ │ REST → gRPC: 入力アダプター追加のみ │ │
│ │ コアは一切変更不要 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. ビジネスロジックの独立性 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ドメインモデルが技術詳細から分離 │ │
│ │ フレームワークに依存しない純粋なビジネスロジック │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. 並行開発 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ チームAはコアを開発 │ │
│ │ チームBはアダプターを開発 │ │
│ │ インターフェースで分離されているので並行可能 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

クリーンアーキテクチャ

Robert C. Martin の提案

クリーンアーキテクチャ = ヘキサゴナルを発展させた、同心円状のアーキテクチャ。

┌─────────────────────────────────────────────────────────────┐
│ クリーンアーキテクチャ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外側: Frameworks & Drivers │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Interface Adapters │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ Application Business Rules │ │ │ │
│ │ │ │ ┌─────────────────────────────┐ │ │ │ │
│ │ │ │ │ Enterprise Business Rules │ │ │ │ │
│ │ │ │ │ (Entities) │ │ │ │ │
│ │ │ │ └─────────────────────────────┘ │ │ │ │
│ │ │ │ (Use Cases) │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ │ (Controllers, Presenters, Gateways) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ (Web, DB, Devices, External Interfaces) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 依存性のルール: 外側から内側への一方向のみ │
│ │
└─────────────────────────────────────────────────────────────┘

各層の役割

┌─────────────────────────────────────────────────────────────┐
│ クリーンアーキテクチャの層 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Entities(エンティティ)- 最内層 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・エンタープライズ全体のビジネスルール │ │
│ │ ・どのアプリからも共通で使える │ │
│ │ ・最も変更されにくい │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Use Cases(ユースケース) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・アプリケーション固有のビジネスルール │ │
│ │ ・ユーザーの操作に対応 │ │
│ │ ・入力→処理→出力の流れを定義 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Interface Adapters(インターフェースアダプター) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・データ形式の変換 │ │
│ │ ・Controller, Presenter, Gateway │ │
│ │ ・外部形式 ↔ 内部形式の橋渡し │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Frameworks & Drivers(フレームワーク&ドライバ)- 最外層 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・技術的な詳細 │ │
│ │ ・Web Framework, DB, UI │ │
│ │ ・「接着剤」のコードが主 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

パターンの比較

各パターンの特徴

┌─────────────────────────────────────────────────────────────┐
│ アーキテクチャパターン比較 │
├─────────────────────────────────────────────────────────────┤
│ │
│ MVC │
│ ├── 適用範囲: UIを持つアプリケーション │
│ ├── 焦点: 表示と処理の分離 │
│ └── 規模: 中小規模 │
│ │
│ レイヤード │
│ ├── 適用範囲: 汎用的なアプリケーション │
│ ├── 焦点: 関心の水平分離 │
│ └── 規模: 中規模 │
│ │
│ ヘキサゴナル │
│ ├── 適用範囲: 外部システム連携が多いアプリケーション │
│ ├── 焦点: コアの独立性、テスト容易性 │
│ └── 規模: 中〜大規模 │
│ │
│ クリーン │
│ ├── 適用範囲: 複雑なビジネスロジックを持つシステム │
│ ├── 焦点: ビジネスルールの独立性 │
│ └── 規模: 大規模エンタープライズ │
│ │
└─────────────────────────────────────────────────────────────┘

選択の指針

観点MVCレイヤードヘキサゴナルクリーン
学習コスト
初期開発速度
テスト容易性
変更への柔軟性
適した規模小〜中中〜大

まとめ

パターン選択の考え方

┌─────────────────────────────────────────────────────────────┐
│ パターン選択のフローチャート │
├─────────────────────────────────────────────────────────────┤
│ │
│ アプリの規模は? │
│ │ │
│ ├── 小規模(CRUD中心)→ MVC / レイヤード │
│ │ │
│ └── 中〜大規模 │
│ │ │
│ └── 外部システムが多い?テスト重視? │
│ │ │
│ ├── Yes → ヘキサゴナル │
│ │ │
│ └── 複雑なビジネスロジック? │
│ │ │
│ ├── Yes → クリーンアーキテクチャ │
│ │ │
│ └── No → レイヤード │
│ │
│ 注意: 最初から複雑なアーキテクチャを選ぶ必要はない │
│ 進化的設計: 必要に応じてリファクタリング │
│ │
└─────────────────────────────────────────────────────────────┘

覚えておくべきこと

パターン核心的な考え方
MVC表示・制御・データの分離
レイヤード水平方向の関心分離
ヘキサゴナルコアを外部から保護、ポート&アダプター
クリーンビジネスルールを中心に、依存は内向きに

このプロジェクト(idp-server)では

┌─────────────────────────────────────────────────────────────┐
│ idp-server のアーキテクチャ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 採用: ヘキサゴナルアーキテクチャ + DDD │
│ │
│ 理由: │
│ ・複数DB対応(PostgreSQL, MySQL) │
│ ・外部システム連携(通知、認証デバイス等) │
│ ・複雑なOAuth/OIDCプロトコル │
│ ・高いテスト容易性の要求 │
│ │
│ 構造: │
│ Controller → UseCase → Core (Handler-Service) → Adapter │
│ │
└─────────────────────────────────────────────────────────────┘

次のステップ


練習問題

Q1: MVCの各コンポーネントの役割を説明してください

答えを見る
  • Model: アプリケーションのデータとビジネスロジック。ViewやControllerに依存しない。
  • View: ユーザーインターフェース。Modelのデータを表示する。
  • Controller: ユーザー入力を受け取り、ModelとViewを制御する。

Q2: ヘキサゴナルアーキテクチャで「ポート」と「アダプター」の違いは?

答えを見る
  • ポート: アプリケーションコアが定義するインターフェース(契約)。「何ができるか」を定義。
  • アダプター: ポートの具体的な実装。技術的な詳細を担当。「どうやるか」を実装。

例:

  • ポート: UserRepositoryインターフェース(findById, saveなどを定義)
  • アダプター: JpaUserRepository(JPAを使ったポートの実装)

Q3: レイヤードとヘキサゴナルの依存方向の違いは?

答えを見る

レイヤード: 上から下へ一方向に依存

Controller → Service → Repository → DB

→ 結局DBに依存してしまう

ヘキサゴナル: 外側から内側に依存(依存性逆転)

Adapter → Port ← Core

→ コアは外部を知らない、アダプターがコアに依存