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

プラグインアーキテクチャ

このドキュメントの目的

クリーンアーキテクチャの依存ルールが、プラグイン構造をどう実現するかを理解することが目標です。


目次

  1. プラグインとは
  2. なぜプラグインが可能になるのか
  3. プラグインの例
  4. 設計のポイント
  5. まとめ

プラグインとは

プラグインの特徴

┌─────────────────────────────────────────────┐
│ プラグインの本質 │
├─────────────────────────────────────────────┤
│ │
│ ・コアを変更せずに機能を追加できる │
│ ・後から差し替えられる │
│ ・組み合わせを自由に選べる │
│ │
└─────────────────────────────────────────────┘

身近なプラグインの例

ブラウザの拡張機能:
├── ブラウザ本体は変わらない
├── 拡張機能を自由に追加/削除
└── 拡張機能同士は独立

エディタのプラグイン:
├── エディタ本体は変わらない
├── 言語サポートを後から追加
└── テーマを自由に切り替え

なぜプラグインが可能になるのか

依存ルールとの関係

┌─────────────────────────────────────────────┐
│ 依存ルールがプラグインを可能にする │
├─────────────────────────────────────────────┤
│ │
│ 依存ルール: │
│ 「コア(内側)はプラグイン(外側)を知らない」│
│ │
│ これが意味すること: │
│ ├── コアはインターフェースだけを定義 │
│ ├── プラグインがそれを実装 │
│ └── コアを変えずにプラグインを追加/変更 │
│ │
└─────────────────────────────────────────────┘

具体的な構造

┌─────────────────────────────────────────────┐
│ プラグイン構造 │
├─────────────────────────────────────────────┤
│ │
│ コア(内側): │
│ ┌─────────────────────┐ │
│ │ NotificationSender │ ← インターフェース │
│ │ (interface) │ │
│ │ + send(message) │ │
│ └─────────────────────┘ │
│ ↑ │
│ │ 実装(プラグイン) │
│ ┌────────┼────────┬────────┐ │
│ ↑ ↑ ↑ ↑ │
│ Email SMS Slack LINE │
│ Sender Sender Sender Sender │
│ │
│ コアはどのSenderが使われるか知らない │
│ Senderは後から自由に追加できる │
│ │
└─────────────────────────────────────────────┘

なぜコアを変えずに済むのか

┌─────────────────────────────────────────────┐
│ コアが知らないから変わらない │
├─────────────────────────────────────────────┤
│ │
│ コアのコード: │
│ 「NotificationSenderにsendを呼ぶ」 │
│ │
│ 新しいプラグインを追加しても: │
│ ├── コアのコードは1文字も変わらない │
│ ├── インターフェースも変わらない │
│ └── 新しい実装を追加するだけ │
│ │
│ 依存が逆転しているから可能 │
│ │
└─────────────────────────────────────────────┘

プラグインの例

データベースの差し替え

┌─────────────────────────────────────────────┐
│ Repository パターン │
├─────────────────────────────────────────────┤
│ │
│ コア: │
│ UserRepository(interface) │
│ + save(user) │
│ + findById(id) │
│ │
│ プラグイン(実装): │
│ ├── PostgreSQLUserRepository │
│ ├── MySQLUserRepository │
│ ├── MongoDBUserRepository │
│ └── InMemoryUserRepository(テスト用) │
│ │
│ コアを変えずにDBを切り替えられる │
│ │
└─────────────────────────────────────────────┘

認証方式の切り替え

┌─────────────────────────────────────────────┐
│ 認証プラグイン │
├─────────────────────────────────────────────┤
│ │
│ コア: │
│ Authenticator(interface) │
│ + authenticate(credentials) │
│ │
│ プラグイン(実装): │
│ ├── PasswordAuthenticator │
│ ├── OAuthAuthenticator │
│ ├── SAMLAuthenticator │
│ └── FIDOAuthenticator │
│ │
│ 新しい認証方式を後から追加できる │
│ │
└─────────────────────────────────────────────┘

外部サービス連携

┌─────────────────────────────────────────────┐
│ 外部サービスプラグイン │
├─────────────────────────────────────────────┤
│ │
│ コア: │
│ PaymentGateway(interface) │
│ + charge(amount) │
│ + refund(transactionId) │
│ │
│ プラグイン(実装): │
│ ├── StripeGateway │
│ ├── PayPalGateway │
│ └── MockGateway(テスト用) │
│ │
│ 決済サービスを切り替えられる │
│ │
└─────────────────────────────────────────────┘

設計のポイント

インターフェースの置き場所

┌─────────────────────────────────────────────┐
│ インターフェースは「内側」に置く │
├─────────────────────────────────────────────┤
│ │
│ 正しい: │
│ コア層にインターフェースを定義 │
│ → プラグインがそれを実装 │
│ → 依存は外側から内側へ ✓ │
│ │
│ 間違い: │
│ プラグイン層にインターフェースを定義 │
│ → コアがそれをimport │
│ → 依存が内側から外側へ ✗ │
│ │
└─────────────────────────────────────────────┘

インターフェースの粒度

┌─────────────────────────────────────────────┐
│ 適切な粒度 │
├─────────────────────────────────────────────┤
│ │
│ 大きすぎる: │
│ DataStore(全DB操作を含む巨大インターフェース)│
│ → 実装が大変、テストも大変 │
│ │
│ 小さすぎる: │
│ UserSaver, UserFinder, UserDeleter... │
│ → インターフェースが増えすぎる │
│ │
│ 適切: │
│ UserRepository(ユーザー操作をまとめる) │
│ → 責務が明確、実装しやすい │
│ │
└─────────────────────────────────────────────┘

プラグインの組み立て

┌─────────────────────────────────────────────┐
│ 誰がプラグインを選ぶか │
├─────────────────────────────────────────────┤
│ │
│ コアは選ばない(知らないから) │
│ │
│ 選ぶのは「最も外側」: │
│ ├── main関数 │
│ ├── 設定ファイル │
│ └── DIコンテナ │
│ │
│ 起動時に「どの実装を使うか」を決定 │
│ コアは渡されたものを使うだけ │
│ │
└─────────────────────────────────────────────┘

まとめ

プラグインと依存ルール

┌─────────────────────────────────────────────┐
│ プラグインを可能にするもの │
├─────────────────────────────────────────────┤
│ │
│ 依存ルール: │
│ 「コアは外側を知らない」 │
│ │
│ これにより: │
│ ├── コアを変えずに機能追加 │
│ ├── 実装を自由に差し替え │
│ └── テスト用の実装も簡単に用意 │
│ │
└─────────────────────────────────────────────┘

設計のポイント

・インターフェースは「内側」に置く
・適切な粒度でインターフェースを設計
・プラグインの選択は「最も外側」で行う

クリーンアーキテクチャの実感

プラグイン構造が自然に実現できるとき
依存ルールが正しく守られている証拠

次のステップ