コンテナ環境での依存性注入 (DI)

コンテナ環境でアプリケーションのコード変更なしの依存性注入パターン (dependency injection pattern) について考えてみます。

横断的関心事をアスペクト指向 (aspect oriented) に解決する方法については別の記事で概要を書いています。

アスペクト指向

少し重複もありますが、再度横断的関心事についても概説したいと思います。

f:id:section27:20220314134428p:plain

左上には、OpenID Provider と AD + AD-FS などの SAML2 フェデレーションによる、認証サービスがあります。ここには、Amazon Cognito Userpool や Keycloak、あるいは IDaaS の各種サービスを利用することができます。ユーザのログイン操作はこのようなサービスを使って実行されます。SAML 2 との連携について、より詳細には「マイクロサービスの Active Directory の活用」を参照してください。

OpenID Provider によって取得した ID トークンの妥当性を検証することで正しく認証されていると判断することができます。Front Layer の Sidecar と接続されている Authentication (認証) によってこの妥当性の検証を行うことができます。これは Envoy proxy 等を Sidecar に使用している場合はコードを書かずに既存の機能で実現可能です (「Envoy を使用して ID Token (OIDC) を検証する」参照してください)。

Authorization (認可) は、システム、アプリケーションによってさまざまなポリシーがあります。今では Open Policy Agent (OPA) というドメイン固有言語を使ってポリシーを適用することをお勧めします。Envoy Proxy を使用した例が「Envoy と Open Policy Agent を使用した認可」にあります。

マイクロサービスレイヤー

Microservices Layer での Authentication (認証) はサービス間認証を意味するため、mTLS などの認証ということになります。しかし、これも Envoy Proxy や Dapr といった Sidecar でサポートされている機能です。

認可は Front Layer と同様に OPA を利用することができます。

依存性注入 (DI) の方法

Front Layer には、JavaScript と Markups (HTML や css) をストレージに配置して変更が容易な UI を提供することが今では一般的です。AWS で S3 に配置した静的コンテンツを Envoy Proxy で提供する詳細については「S3 の静的 Web サイトを Envoy でホスティング」を参照してください。

UI が利用する API は、Front Layer に配置されます (図の 「API for Frontend」)。

ここで、この API でさまざまなシステムの機能を提供するように作成することができます。しかし、皆さんは、これまで Spring や CDI といった dependency injection pattern を使ってコードを書いてきていませんでしょうか? なぜ、そうしてきたのでしょうか?

著名な「CLean Architecture」をはじめとして、変更に強く、生産性が高く、そして保守性の良いアーキテクチャを実現できる利点から、多くのソフトウェアで DI パターンが採用されてきました。

DI パターンを利用することで得られる利点をあらためて列挙してみましょう。

  • 各レイヤーのモジュールを単一の責務で実装できる
  • レイヤー間のインターフェースの取り決めにより、それぞれのレイヤーのモジュールの差し替えが容易になる (契約による設計)。

他にも利点はありますが、上記のようにモジュールの差し替えが可能なポイントを、インジェクションポイントとも言います。逆にいうと、インジェクトションポイントが適切なところになければ、モジュールの差し替えは困難となります。これは、単に Spring や CDI といったフレームワークを利用しているかどうかとは別の設計上の問題です。

同様に、コンテナ環境でインジェクションポイントを設けるためにはどうすれば良いでしょうか。

ここまで横断的関心事については、Sidecar により実現できることを説明してきました。同様に、DI についても Sidecar によって実現することが可能です。

THE Twelve-Facor Apps」でも、依存関係を明示的に宣言し分離する、ポートバインディングを通してサービスを公開する、プロセスモデルによってスケールアウトする等々のベストプラクティスを実装するために、Sidecar とそれぞれの責務を分担するコンテナは理想的だと考えています。

Front Layer にある「API for Frontend」コンポーネントや Microservices Layer にある「Service」コンポーネントの I/F がインジェクションポイントとなり、たとえば、図にあるように Kafka などのメッセージング基盤の「Publisher」、「Subscriber」それと「HTTP Gateway」のコンポーネントを Sidecar に設定することで、実装された「API for Frontend」、「Service」コンポーネントへのリクエストを HTTP Gateway と Subscribe に切り替えたり、あるいはレスポンス先を HTTP Gateway と Publisher に切り替えることで同期型から非同期型への変更が容易になります。HTTP のままであったとしても、Blue/Green やカナリアのためにバージョンの異なるコンポーネントに振り分けることもできます。

図にはありませんが、たとえば、データベースアクセスに特化した I/F を持つコンテナを設計したとすれば、PostgreSQLMySQL、DynamoDB などの NoSQL 等にアクセスするコンテナを用意して、それぞれのユーザ環境への対応が容易になります。

まとめ

Spring や CDI など、これまでコード内のレイヤー分割で必要とされてきた依存性注入 (DI) の概念を Sidecar パターンを利用してコンテナ間で可能とする方法について説明しました。

ただし、DI を活用するためには誰もが理解できるインジェクションポイントが重要であり、Spring や CDI で培ってきたレイヤーアーキテクチャの重要性は変わっていないことに注意してください。