ライブラリBに依存するコンソールアプリ(A)を構築しています。ライブラリBはさらにライブラリCに依存しています。3つのエンティティすべてを構築しています。これまでに完了した開発のほとんどは、機能設計を使用して行われており、これまでのところこれはうまく機能しており、現在ステートレスであり、インスタンス化はこれまで必要ありませんでした。現在、いくつかの依存関係を宣言し、必要に応じてそれらを注入する必要がある段階にあります。ライブラリBとCの間の相互作用は、アプリケーションAに対して透過的である必要があります。依存関係の注入/制御の反転について調べてきましたが、依存関係の注入フレームワークとしてinversifyを使用することにしました。私が「行き詰まっている」1つの点は、コンポジションルートの概念と、それらを作成する場所です。私はこの記事を見つけました ' https://blog.ploeh.dk/2011/07/28/CompositionRoot/ 'これは、CRがアプリケーションのメインエントリポイントの近くにある必要があることを示しています。また、CRは、アプリケーションだけのライブラリには適用できないとも述べています。ただし、ライブラリBとCの間の相互作用は、クライアントが認識してはならない実装の詳細です。それで、この問題の正しい解決策は何ですか?ライブラリCのコンポーネントへの依存関係を注入できるように、ライブラリBでCRを定義する傾向があります。これは私には正しいように見えますが、確立された知恵とは矛盾しているようです。記事を引用すると、「アプリケーションだけがコンポジションルートを持つべきです。ライブラリとフレームワークは持つべきではありません。」これを正しく行うには、クライアントアプリは、コンポジションルートを介してすべてを構成するために、すべての実装クラッドとそのクライアントまでを認識する必要があるようです。
しかし、ライブラリBに依存関係を注入する必要がある場合(ライブラリCからのもの)、これをどのように行うのですか?私は今分析麻痺に苦しんでいます...
PS、私はこの記事も検討しました: 構成ルートでコンストラクタを介してクロスカッティングパラメータを渡すための設計/アーキテクチャ? ですが、これはアプリケーション/クライアントがダウンストリームの依存関係を認識している問題には対処しません。
その記事には、2つの微妙に異なる一般的な推奨事項があります。
1)「アプリケーションのみがコンポジションルートを持つべきです。ライブラリとフレームワークは持つべきではありません。」
2)「DIコンテナはコンポジションルートからのみ参照する必要があります。他のすべてのモジュールはコンテナを参照しないでください。」
あなたが言った:
これを正しく行うには、クライアントアプリは、コンポジションルートを介してすべてを構成するために、すべての実装クラッドとそのクライアントまでを認識する必要があるようです。
ここでの問題の1つは、ライブラリの(人間の)ユーザーがBを使用するためにCについて知って理解している必要があるように見えることです。必ずしもそうとは限りません。しかし、DIに関しては同じページにいるように、少し逸脱させてください。
クライアントアプリケーションとコンポジションルートを2つの別個のコンポーネント(同じモジュール/パッケージ/ライブラリにある場合でも)と考え、そのように設計します。コンポジションルートは、構成されているすべてのものを参照(認識)します(実際、その事実は他のコンポーネントの分離に大きく貢献しています)。 全体アプリケーションが構成される単一の場所になることを意図しています。 CはコンポジションルートによってBに注入されるため、コアアプリケーション自体はCについて何も知る必要はありません。これは次のようになります(一部の矢印は省略されています。CRはインターフェイスについても認識しています)。
Cが(この特定の設計では)交換可能であるという意味で、BとCは分離されていることに注意してください。アプリケーションのコンポジションルートにBとCをコンポジションさせると、クライアントがCを別の実装(おそらくサードパーティによって開発されたものでも)に簡単に交換できるため、より柔軟性があります。また、これらの各コンポーネントを個別にテストすることもできます。さて、時々それはCだけでは実際にはそれほど役に立たない場合があり、そのためこれらの考慮事項の関連性がやや低くなることがあります。
1)「アプリケーションのみがコンポジションルートを持つべきです。ライブラリとフレームワークは持つべきではありません。」
この発言は、少なくとも部分的には、私が上で説明したことに動機付けられていると思います。
しかし、 別の記事 で、彼はDI対応のライブラリを設計する方法について書いています。 「ファサードを検討する」という名前のセクションがあります。
一部のオブジェクトのクラスには複雑なコンストラクターがあるために構築が難しい場合は、適切な依存関係の適切なデフォルトの組み合わせをファサードに提供することを検討してください。
あなたの場合、これは、Bのクライアントが始めるためにCについて知ったり理解したりする必要はないという考えにうまく適合します。したがって、便利なFacadeを使用すると、柔軟性があり、1つ以上の事前構成済みのデフォルトを使用できます(この場合、現時点でのオプションは1つだけです)。このファサードは、記事で説明されているように、Seemannが呼び出すスタイルのライブラリのコンポーネントを実際に構成することに注意してください Pure DI なので、これはローカル構成ルートであると主張しますが、彼はこれを構成ルートパターンとは見なしていないようです。
それは概念的なもののように見えます。私は今推測していますが、コンポジションルートの彼の定義は、何か(潜在的に、依存関係の複雑なグラフ)が全体として組み立てられる専用の単一の場所であると私には思われますそのため、クライアントコードによって実行時に構成可能であることを実際には意図していません(したがって、DIコンテナーとは異なりますが、DIコンテナーのようではありません。基本的に、仕様に基づいて組み立てられたものを生成します。コンベンション)。この種の柔軟性をクライアントに提供するために考えることができるインターフェイスは、クライアント自体に依存関係を構築させるだけで、それほど強力ではないか、より複雑になります。したがって、ライブラリ(またはフレームワーク)にコンポジションルートがあり、最終結果(コンポジット)を返すだけの場合、そのデカップリングとモジュール性のほとんど(またはすべて)がクライアントの観点から失われます。繰り返しますが、これは彼がそれについて書いている方法から私が得る印象ですが、多分私は間違っています。
とにかく、最終的には、ライブラリに適切なデフォルトを提供することは問題ありませんが、クライアントが希望する場合は、クライアントが選択した依存関係(独自の「C」)を注入できるようにすることをお勧めします。これにより、BとCの両方がより再利用可能になります(これらのライブラリは、開発中に予期していなかったシナリオをより簡単にサポートするため、他の人にとってより便利になる可能性があります)。内部DIコンテナを使用してこれらのデフォルトを提供することは、多くの場合、おそらくやり過ぎです。
他のステートメントについては:
2)「DIコンテナはコンポジションルートからのみ参照する必要があります。他のすべてのモジュールはコンテナを参照しないでください。」
この行の直前の彼の book で、Seemannは、(単一の)DIコンテナーをサービスロケーターとして誤用しないように注意する方法について説明しています。それがこの推奨の背後にある主な動機であるという印象を受けています。 Service Locatorは事実上、クラスがコードベースのどこにでも(多かれ少なかれ)依存関係を要求するために内部的に使用できるグローバルオブジェクトであり、これは さまざまな方法 で悪いことが判明する可能性があります。
コードの保守性に関連する他のいくつかの懸念があります。 参照した記事 を開いて少し下にスクロールすると、Seemannが書いているコメントがあります。
各モジュールに独自のタイプを任意の種類のコンテナーに登録させることの問題は、モジュール(およびモジュールを使用する可能性のあるアプリケーション)を特定のコンテナーに緊密に結合することです。さらに悪いことに、あるモジュールが提供するサービスを別のモジュールから消費する必要がある場合は、これら2つを互いに結合する必要があります。間もなく、緊密に結合された混乱が発生します。
ただし、このコメントは少し手が動いています。正確に方法に大きく依存しているためです。たとえば、前述のファサードもライブラリをその依存関係に結合しますが、結合はファサードに限定されます。ライブラリのコアコンポーネントは分離されたままです。これらの懸念はもう少し高レベルであり、進化するコードベースで問題になる可能性があります。そこでは、依存関係を注意深く制御し、それらの変更をサポートして複雑さを管理するために物事を改造する必要があり、設計を効果的に伝達する必要があります。同じシステムで作業している他の開発者へのアーキテクチャ。