web-dev-qa-db-ja.com

デザイン-Windsorを使用するときにオブジェクトを登録する場所

アプリケーションには次のコンポーネントが含まれます

  • データアクセス
  • DataAccess.Test
  • ビジネス
  • Business.Test
  • 応用

Castle WindsorをIoCとして使用してレイヤーを接着したいと思っていましたが、接着のデザインについては少し不確かです。

私の質問は、オブジェクトをWindsorに登録する責任があるのは誰ですか?私にはいくつかのアイデアがあります。

  1. 各レイヤーは、独自のオブジェクトを登録できます。 BLをテストするために、テストベンチはDALのモッククラスを登録できます。
  2. 各レイヤーは、その依存関係のオブジェクトを登録できます。ビジネス層は、データアクセス層のコンポーネントを登録します。 BLをテストするには、テストベンチで「実際の」DALオブジェクトをアンロードし、モックオブジェクトを登録する必要があります。
  3. アプリケーション(またはテストアプリ)は、依存関係のすべてのオブジェクトを登録します。

誰かがさまざまなパスでいくつかのアイデアや賛否両論を手伝ってくれますか?このようにCastleWindsorを利用するサンプルプロジェクトへのリンクは非常に役立ちます。

47
Fredrik Jansson

一般に、アプリケーション内のすべてのコンポーネントは、最大限のモジュール性を保証し、モジュールが可能な限り疎結合であるため、できるだけ遅く構成する必要があります。

実際には、これは、アプリケーションのルートでコンテナーを構成する必要があることを意味します。

  • デスクトップアプリでは、それはMainメソッドになります(またはそれに非常に近い)
  • ASP.NET(MVCを含む)アプリケーションでは、Global.asaxにあります
  • WCFでは、これはServiceHostFactoryにあります
  • 等.

コンテナは、モジュールを機能するアプリケーションに構成するエンジンです。原則として、手作業でコードを書くことができます(これはPoor Man's DIと呼ばれます)が、次のようなDIコンテナを使用する方がはるかに簡単です。ウィンザー。

このようなComposition Rootは、理想的にはアプリケーションのルート内の唯一のコードであり、アプリケーションをいわゆるHumble Executable(優れた xUnitテストパターン からの用語)それ自体はユニットテストを必要としません。

オブジェクトとモジュールは構成可能である必要があり、単体テストから直接テストダブルを提供できるため、テストにはコンテナーはまったく必要ありません。 。すべてのモジュールをコンテナに依存しないように設計できるのが最善です。

また、特にWindsorでは、コンポーネント登録ロジックをインストーラー(IWindsorInstallerを実装するタイプ)内にカプセル化する必要があります。詳細については、 ドキュメント を参照してください。

75
Mark Seemann

マークの答えはWebシナリオには最適ですが、すべてのアーキテクチャ(つまり、リッチクライアント-つまり、WPF、WinForms、iOSなど)に適用する際の主な欠陥は、操作に必要なすべてのコンポーネントを作成できる/作成する必要があるという前提です。すぐに。

Webサーバーの場合、これは理にかなっています。これは、すべての要求が非常に短命であり、ASP.NET MVCコントローラーが受信するすべての要求に対して基盤となるフレームワーク(ユーザーコードなし)によって作成されるためです。したがって、コントローラーとそのすべての依存関係を簡単に構成できます。 DIフレームワークによるものであり、そうするためのメンテナンスコストはほとんどありません。 Webフレームワークは、コントローラーの存続期間と、そのすべての依存関係の存続期間(DIフレームワークがコントローラーの作成時に作成/挿入する)を管理する責任があることに注意してください。依存関係がリクエストの期間中存続し、ユーザーコードがコンポーネントとサブコンポーネント自体の存続期間を管理する必要がないことはまったく問題ありません。また、Webサーバーはさまざまな要求間でステートレスであり(セッション状態を除くが、この説明には関係ありません)、単一の要求を処理するために同時に存在する必要のある複数のコントローラー/子コントローラーインスタンスが存在しないことにも注意してください。

リッチクライアントアプリでは、これはほとんど当てはまりません。MVC/MVVMアーキテクチャを使用している場合(そうすべきです!)、ユーザーのセッションは長くなります-リビングとコントローラーは、ユーザーがアプリ内を移動するときにサブコントローラー/兄弟コントローラーを作成します(下部のMVVMに関する注記を参照)。 Webの世界との類似性は、リッチクライアントアプリでのすべてのユーザー入力(ボタンのクリック、実行される操作)は、Webフレームワークによって受信される要求と同等であるということです。ただし、大きな違いは、リッチクライアントアプリのコントローラーが操作間で存続すること(ユーザーが同じ画面で複数の操作を実行する可能性が非常に高い-特定のコントローラーによって制御される)と、サブコントローラーが取得することです。ユーザーがさまざまなアクションを実行すると作成および破棄されます(ユーザーがタブに移動した場合にタブを遅延作成するタブコントロール、またはユーザーが画面上で特定のアクションを実行した場合にのみロードする必要があるUIの一部について考えてみてください)。

これらの両方の特性は、コントローラー/サブコントローラーの存続期間を管理する必要があるのはユーザーコードであり、コントローラーの依存関係をすべて事前に作成する必要がないことを意味します(つまり、サブコントローラー、ビューモデル、その他のプレゼンテーションコンポーネントなど)。 DIフレームワークを使用してこれらの責任を実行すると、それが属していないコードがさらに多くなるだけでなく( コンストラクターのオーバーインジェクションアンチパターン を参照)、必要になります。コンポーネントが必要なときにサブコンポーネントを作成するためにそれを使用できるように、プレゼンテーション層のほとんど全体に依存関係コンテナーを渡します。

ユーザーコードがDIコンテナにアクセスできるのはなぜ悪いのですか?

1)依存関係コンテナーは、アプリ内の多くのコンポーネントへの参照を保持します。この悪者をアノッターサブコンポーネントを作成/管理する必要のあるすべてのコンポーネントに渡すことは、アーキテクチャーでグローバルを使用することと同じです。さらに悪いことに、サブコンポーネントは新しいコンポーネントをコンテナに登録する可能性があるため、すぐにグローバルストレージにもなります。開発者は、コンポーネント間(兄弟コントローラー間または深いコントローラー階層間)でデータを渡すためだけにオブジェクトをコンテナーにスローします。つまり、祖先コントローラーは祖父母コントローラーからデータを取得する必要があります。 コンテナがユーザーコードに渡されないWebの世界では、これが問題になることはありません。

2)依存関係コンテナとサービスロケータ/ファクトリ/直接オブジェクトのインスタンス化に関する他の問題は、コンテナから解決すると、コンポーネントを作成するのか、単に既存のコンポーネントを再利用するのかが完全にあいまいになることです。代わりに、コンポーネントの存続期間を把握するために、一元化された構成(つまり、ブートストラップ/コンポジションルート)に任されています。場合によっては、これで問題ありません(つまり、コンポーネントの存続期間を管理する必要があるのはユーザーコードではなく、ランタイム要求処理フレームワーク自体であるWebコントローラー)。ただし、これは非常に問題があります。ただし、コンポーネントの設計で、コンポーネントを管理する責任があるかどうか、およびコンポーネントの寿命を示す必要がある場合(例:電話アプリがユーザーに情報を求めるシートをポップアップ表示します。これは、コントローラーは、オーバーレイシートを管理するサブコントローラーを作成します。ユーザーが情報を入力すると、シートは辞任され、制御は最初のコントローラーに戻ります。初期コントローラーは、ユーザーが以前行っていた状態を維持します。 DIを使用してシートサブコントローラーを解決する場合、その存続期間はどうあるべきか、または誰がそれを管理する責任を負うべきか(開始コントローラー)はあいまいです。これを、他のメカニズムの使用によって指示された明示的な責任と比較してください。

シナリオA:

// not sure whether I'm responsible for creating the thing or not
DependencyContainer.GimmeA<Thing>()

シナリオB:

// responsibility is clear that this component is responsible for creation

Factory.CreateMeA<Thing>()
// or simply
new Thing()

シナリオC:

// responsibility is clear that this component is not responsible for creation, but rather only consumption

ServiceLocator.GetMeTheExisting<Thing>()
// or simply
ServiceLocator.Thing

ご覧のとおり、DIを使用すると、サブコンポーネントのライフタイム管理の責任者が不明確になります。

注:技術的に言えば、多くのDIフレームワークには、コンポーネントを遅延作成する方法があります(参照: 依存性注入を行わない方法-静的またはシングルトンコンテナ )これはコンテナを渡すよりもはるかに優れていますが、コードを変更して作成関数をどこにでも渡すためのコストを支払っています。作成中に有効なコンストラクタパラメータを渡すための第1レベルのサポートがありません。そして、結局のところ、テスト容易性を達成することが唯一の利点である場所で、不必要に間接メカニズムを使用しています。これは、より優れた、より簡単な方法で達成できます(以下を参照)。

これはどういう意味ですか?

これは、DIが特定のシナリオに適切であり、他のシナリオには不適切であることを意味します。リッチクライアントアプリケーションでは、DIの多くの欠点がありますが、利点はほとんどありません。アプリの複雑さが増すほど、メンテナンスコストは大きくなります。また、誤用の重大な可能性もあります。これは、チームのコミュニケーションとコードレビューのプロセスがどれほど緊密であるかに応じて、問題のないものから深刻な技術的負債のコストまでさまざまです。 Service LocatorやFactory、または古き良きインスタンス化は、おそらく多くの人が参加するWebアプリの世界では最適なメカニズムではない可能性があるという理由だけで、どういうわけか古くて時代遅れのメカニズムであるという神話があります。これらの学習をすべてのシナリオに一般化し、特定のハンマーを使用することを学んだという理由だけで、すべてを釘と見なします。

私の推奨事項リッチクライアントアプリの場合は、手元の各コンポーネントの要件を満たす最小限のメカニズムを使用することです。 80%の場合、これは直接インスタンス化する必要があります。サービスロケーターは、主要なビジネスレイヤーコンポーネント(つまり、一般的にシングルトンであるアプリケーションサービス)を格納するために使用できます。もちろん、ファクトリやシングルトンパターンもその場所を持っています。 サービスロケーターの背後に隠されたDIフレームワークを使用して、ビジネスレイヤーの依存関係とそれらが依存するすべてのものを一度に作成できないことは言うまでもありません-それがあなたの人生を楽にしてしまうならそのレイヤーでは、そのレイヤーは、リッチクライアントプレゼンテーションレイヤーが圧倒的に行う遅延読み込みを示しません。 DIコンテナを渡すことで発生する可能性のある混乱を防ぐことができるように、ユーザーコードがそのコンテナにアクセスできないように保護してください。

テスト容易性はどうですか?

テスト容易性はDIフレームワークなしで絶対に達成できます。nitBox (無料)または TypeMock (高価)などのインターセプトフレームワークを使用することをお勧めします。これらのフレームワークは、目前の問題を回避するために必要なツール(C#でインスタンス化と静的呼び出しをどのようにモックアウトするか)を提供し、それらを回避するためにアーキテクチャ全体を変更する必要はありません(残念ながらトレンドがあります) .NET/Javaの世界に行きました)。手元にある問題の解決策を見つけ、基礎となるコンポーネントに最適な自然言語のメカニズムとパターンを使用してから、すべての四角いペグを丸いDI穴に合わせようとする方が賢明です。これらのより単純でより具体的なメカニズムを使い始めると、コードベースにDIが必要な場合はほとんどないことに気付くでしょう。

注:MVVMアーキテクチャの場合

基本的なMVVMアーキテクチャでは、ビューモデルは事実上コントローラーの責任を引き受けるため、あらゆる目的で、上記の「コントローラー」の表現を「ビューモデル」に適用することを検討してください。基本的なMVVMは小さなアプリでは問題なく機能しますが、アプリの複雑さが増すにつれて、MVCVMアプローチを使用することをお勧めします。ビューモデルは、ビジネスレイヤーとの対話中、および画面/サブ画面を表すビューモデルのグループ間の相互作用が明示的なコントローラー/サブコントローラーコンポーネントにカプセル化される間、ビューへのデータバインディングを容易にするためにほとんどダムDTOになります。どちらのアーキテクチャでも、コントローラの責任が存在し、上記と同じ特性を示します。

25
Marchy