マイクロソフトのpatterns & practicesグループは、UnityもしくはUnity Application Blockと呼ばれる依存性注入コンテナをリリースした。開発者はこの軽量コンテナを使い、拡張可能な疎結合アプリケーションを作成できるようになった。
InfoQは、Unityプロジェクトの開発リーダー、Chris Tavares氏と話す機会を得た。
Rob Bazinet (RB): Chrisさん、まず自己紹介とUnityとの関わりについて教えてください。
Chris Tavares (CT): マイクロソフトpatterns & practicesグループの上級ソフトウェア開発者、Chris Tavaresです。現在はEnterprise Library 4とUnity Application Blockの開発リーダーを務めています。Unityコードの大部分も書いており、Unityはほとんど私の責任です。patterns & practicesに来てから2年ちょっとになります。マイクロソフトに来る前は、請負や市販製品の開発、ずっと昔の90年代には組み込みソフトウェアに も少々携わり、この業界のあちこちで働いていました。
RB: Unity Application Blockを説明していただけますか。
CT: Unityは依存性注入(DI)のコンテナです。DIを説明している基準となる論文はMartin Fowler氏によるものです[0]。 簡単にまとめると、DIコンテナは、高度にデカップルしたソフトウェア構築を支援するために使うことができるツールです。DIコンテナは、どのオブジェク トが相互接続しているかについて細部まで管理するため、各オブジェクトを個別に構築することができるのです。これは結果として生じるコードのテスト容易性 と柔軟性の両方に、大きな影響を与えます。
たとえば銀行のシステムには、口座振替を管理するオブジェクトがあるでしょう。このオブジェクトは個々の口座オブジェクトを把握する必要があり、さらに、セキュリティ規則と会計監査の必要条件もあります。一般の実装は次のようなものになります。
public class AccountTransfer
{
public void TransferMoney(int sourceAccountNumber, int destAccountNumber, decimal amount)
{
Account sourceAccount = AccountDatabase.GetAccount(sourceAccountNumber);
Account destAccount = AccountDatabase.GetAccount(destAccountNumber);
sourceAccount.Withdraw(amount);
destAccount.Deposit(amount);
Logger.Write("Transferred {0} from {1} to {2}", amount, sourceAccountNumber, destAccountNumber);
}
}
かなりひどいコードに違いありませんが(たとえば、トランザクションの管理がありません)、ここでの役割は果たしています。;-)
かなりひどいコードに違いありませんが(たとえば、トランザクションの管理がありません)、ここでの役割は果たしています。;-) かなり単純明快ですが、環境への結合度も高くなっています。グローバルなAccountDatabaseク ラスの呼び出しは、テストは言うまでもなく、コンパイルさえ、それ自体ではできないことを意味します。2つの異なる銀行の口座だったら、一体どうなってし まうのでしょう。同様に、グローバルなLoggerは、ロガーだけの不在時に限らず、この特定のグローバルなロガークラスの不在時にも、このクラスを使う ことができないことを意味します。その結果、単体テストを書こうとすると非常に苦労することになり、長期的に見れば、柔軟性が大いに制限されることになり ます。
関心の分離の原則では、クラスは複数のことをすべきではないことになっています。ここでは、送金方法の詳細だけでなく、データベースからの口座の取り出し やロギングメッセージの書き方にまでハードワイヤしているため、このクラスは原則に違反しているのです。柔軟性を復活させるために、こうした関心事は別々 のオブジェクトに入れるべきであり、次に、そうしたオブジェクトを使うオブジェクトに渡せばいいのです。こんな感じです。
public class AccountTransfer
{
private IAccountRepository accounts;
private ILogger logger;
public AccountTransfer(IAccountRepository accounts, ILogger logger)
{
this.accounts = accounts;
this.logger = logger;
}
public void TransferMoney(int sourceAccountNumber, int destAccountNumber, decimal amount)
{
Account sourceAccount = accounts.GetAccount(sourceAccountNumber);
Account destAccount = accounts.GetAccount(destAccountNumber);
sourceAccount.Withdraw(amount);
destAccount.Deposit(amount);
logger.Write("Transferred {0} from {1} to {2}", amount, sourceAccountNumber, destAccountNumber);
}
}
これで目的に少し近づきました。これで、外部のグローバルオブジェクトではなく、コンストラクタに渡されるインスタンスのみに依存するようになりました。 クラスを隔離してテストできるようになり、IAccountRepositoryの別の実装を通ることにより、別の銀行との通信さえ可能になりました。
しかしながら、新たな犠牲が発生しています。AccountTransferの クリエータは、必要とされる従属オブジェクトの作成方法を知っていなければならなくなりました。どの口座リポジトリを使っていますか。ロガーはどれです か。たとえば、口座リポジトリやロガーをコンフィギュレーションで設定しているのなら、いたるところでコンフィギュレーションに依存したコードが出来上 がっているわけで、振り出しに戻ってしまったことになります。
ここで、依存性注入コンテナの出番です。依存性注入コンテナは情報処理能力を持つオブジェクトファクトリです。特定オブジェクトの依存性の解決方法をコン テナに教えます。たとえばUnityを使えば、次のようにコンテナを設定できます(このAPIを使えば、外部コンフィギュレーションファイルに対するサ ポートも提供します)。
IUnityContainer container = new UnityContainer();
container.RegisterType
何をしているのかというと、「リゾルブするオブジェクトがIAccountRepositoryのインスタンスに依存している場合は、ContosoBankRepositoryを作成して使うこと。ILoggerを必要とするものがあれば、DatabaseLoggerを与えなさい」と、コンテナに命令しているのです。今度は次のようにして、依存性のあるオブジェクトのインスタンスを引き渡すよう、コンテナに要請できます。
container.Resolve();
Resolve呼び出しはAccountTransferイ ンスタンス作成を試みます。コンストラクタがIAccountRepositoryとILoggerを必要としていることがコンテナには分かっているの で、コンテナはその2つを(以前指定された具象型を使って)作成し、コンストラクタに渡します。このようにコンテナを使えば、アプリケーションの全ワイヤ リングを中央集中化できます。これにより(大抵の場合は)、オブジェクト同士のフッキングに対処する場所がアプリケーション内に一箇所だけ確保され、オブ ジェクトグラフを構築する責務から個々のオブジェクトが解放されます。結果として柔軟性が生まれ、テスト容易性と柔軟性の両面で実に効果をもたらします。 そして、クラスの進化に伴って依存性が変化すれば、コンテナのコンフィギュレーションに影響を及ぼすだけで、オブジェクトの作成には影響しません。
RB: UnityはEnterprise Libraryの一部でしょうか、それとも独立しているのでしょうか。これまでに目にした情報の中には、Microsoft Dependency Injection BlockはEnterprise Library 4.0の一部としてリリースされると書いているものもありました。
CT: Unityは独立しています。Enterprise Library 4はUnityのパーツの上に構築されており、Unityを使ってEnterprise Libraryの機能にアクセスできます。
過去について少し紹介すれば、おそらく混乱を解決できるでしょう。Enterprise Library 2とComposite UI Application Block(CAB)がリリースされたとき、両者の傘の下にObjectBuilderという小さなライブラリがありました。ObjectBuilderは、依存性注入コンテナの構築に使うフレームワークでした。CABとEnterprise Libraryの両方がOBを消費しましたが、OB自体は独立しており、後に単独で出荷されました[1]。
Unityの一部はObjectBuilderの更新版です。Enterprise Library 4はこれまでと同様に、コンフィギュレーションの読み取りや適切なEnterprise Libraryオブジェクトの構築に、ObjectBuilderを利用し続けています。また、Enterprise Libraryへの新しいアクセス方法も導入していますが、その方法とは、メカニズムを隠すよりもむしろ、コンテナを直接介してリゾルブすることです。Scott Densmoreのブログポスト[2]では、私たちの計画について、さらに細部まで説明しています。
もう一度説明すると、Unityはスタンドアロンのブロックです。Enterprise LibraryはUnityを部分的に使うか、Unityと一緒に使えます。ダウンローダの問題をなくすことを目的として、Enterprise LibraryにはUnityバイナリが含まれています。ですから、Enterprise Libraryを使うことだけに関心をお持ちなら、他に何もインストールする必要もなく、準備万端です。
RB: どのような状況で開発者はUnityの使用を選択するのでしょうか。
CT: 最初に決める問題は、依存性注入を望んでいるか、あるいは必要としているかです。もしそうなら、Unityは良い選択と考えますが、代わりとなる他のコンテナを探すことも提案します。Scott Hanselman氏は.NETのDIコンテナプロジェクトについてリスト化[3]を始めています。
RB: Unityは他のDIコンテナとどう違うのでしょうか。また、比較してどうなのでしょうか。
CT: patterns & practicesの立場で話しているので、この手の質問には細心の注意を払わなければなりません。外部のプロジェクトの使用を勧めたり、反対したりして いるような印象を与えたくないのです。皆がオプションを評価し、Unityあるいは既存のオープンソースプロジェクトから、ニーズを一番よく満たすコンテ ナを選択するよう、強くお勧めします。
RB: DIコンテナはすでにかなり多数存在しますが、あなたのチームがUnityを制作する上で、何がモチベーションとなったのでしょうか。
CT: Patterns & practicesが依存性注入に関して指導的役割を担うようになってから、しばらくになります。CAB、Mobile client Software Factory、Smart Client Software Factory、Web Client Software Factory、Enterprise Libraryはすべて、多種多様な方法でDIを使ってきました。「多種多様」という言葉がくせ者です。プロジェクトはすべてObjectBuilder上に構築されましたが、それぞれのDIの方法は異なり、互換性がありませんでした。何の妨げもなく使用できる、十分な機能を持つ明示的なコンテナにより、DIならびにコンテナベースのアーキテクチャに関して、より良いガイダンスを提供することができます。
他にも理由があります。どういうわけか、オープンソースソフトウェアには手を出さない顧客もいます。マイクロソフトが提供するDIコンテナがあれば、顧客 はより安全に感じ、メリットも得られます。後にコンテナを切り替えたければ、それにも対応できる好適ポジションに立つことができるのです。
もう1つの目標は、マイクロソフトの内外両方で依存性注入に注目を集めることです。マイクロソフトからコンテナを出すことは、マイクロソフトの.NET開発者コミュニティ一般とマイクロソフトの内部開発者の両方でDIを普及させるのに役立ちます。
RB: 開発者やチームがUnityに取り掛かる最良の方法を提案していただけませんか。
CT: ダウンロードし(source)、インストールしたら、ドキュメントを最初から最後まで読み、同梱している簡単なクイックスタートをご覧ください。
RB: Unityのクイックスタートにはベストプラクティスやサンプルコードはありますか。
CT: コンテナをサービス注入に使用するWindows Formsアプリケーション(Stoplight Simulator)の小規模な例があります。これは小さく、扱いやすい例で、C # とVB .NETバージョンがあります。
RB: Unityの将来の計画はどうなっていますか。
CT: もちろん、確定して変更できないものなど、何もありません。私の個人的な目標は、いくつかの機能追加(最優先は、メソッド呼び出しのインターセプト機能) と、コンテナベースのアーキテクチャ上で将来のpatterns & practicesの資産を標準化することです。さらに優れたコンフィギュレーションファイルシステムも魅力的ですね。
長期的に見れば、こうしたコンセプトの一部あるいは全部を合理的な方法で、ぜひともコア・プラットフォームに入れたいと思います。
RB: Chrisさん、本日は時間を割いていただき、Unityに関するすばらしい情報をありがとうございました。
UnityのWebサイトによれば、Unityは次のとおりである。
Unity Application Block(Unity)は、軽量で、拡大可能な依存性注入コンテナです。疎結合のアプリケーション構築を容易にし、開発者に以下の利点を提供します。
- 簡略化したオブジェクト作成。特に階層的なオブジェクト構造および依存性
- 要件の抽出。これにより開発者は、ランタイムもしくはコンフィギュレーション時に依存性を指定でき、横断する関心事の管理を簡略化できる
- コンテナにコンポーネントのコンフィギュレーションを委ねることにより、柔軟性が向上
- サービスロケーション機能。クライアントはコンテナを記憶もしくはキャッシュできる
Unityに取り掛かる上での概要を把握するため、開発者はIntroduction to Unity(Unityの紹介)(source)を見た方が良いだろう。Unity Application Blockに関する追加情報はPatterns and PracticesのWebサイト(source)にあり、CodePlexのWeb サイト(source)からダウンロードできる。Tavares氏の今後についてはブログ(ブログ・英語)でご覧あれ。Enterprise Library 4に投入される依存性注入については、2007年12月のInfoQの記事「Microsoft Enterprise Library 4.0 will get a dose of Dependency Injection」(MicrosoftのEnterprise Library 4.0、依存性を注入される予定)(参考記事・英語)で紹介した。
[0] http://martinfowler.com/articles/injection.html
[1] http://www.codeplex.com/ObjectBuilder
[3] http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx