さてさて、
社内では、いろいろとリリース作業におわれている今日この頃
ところで、
修正やリリースなど新しいソースコードで、トラブルなど見舞われた場合どうしてますか?
ソースコードを元に戻すのがとりあえずの対処法ですが、
そういう時に、前のメソッドを呼ぶように直そう・・・と思っても、
実際には、前のコードに直したり、ビルドし直して再配置したりと
かなりめんどうな作業になりますね。
そういう対処に備える方法論としてDIコンテナーという概念があります。
.NET系ではそれほど目立つ存在ではないのですが、
JAVAの世界では、フレームワーク側の考えで当たり前のようになっています。
その前に、DIとは何かという事に触れておきましょう。
DI(Dependency Injectionの略)とは、依存性の注入と訳されて良く言われます。
クラス継承の関係から実装を取り除き、依存性を少なくするデザインパターンです。
具体的には、インターフェースクラスと実装クラスに分離させて設計し、
物理的に、利用する側はインターフェース型のインスタンス変数に実装クラスを注入して利用するパターンです。
仮想メソッドを宣言した基底クラスとしても良いでしょう。
クラス設計をする人には、特に難しくは無いでしょう。
ここで、依存性の注入というところがミソで、
生成する実装クラスが、定義によって決める事ができるというのが、
DIコンテナーの大きな役割です。
以下.NETで扱う方法を簡略にまとめました。
実際に、今後の開発指針として組み入れたい物です。
.NETでDIを実装する方法
1. Assemblyを読み込み、Reflection(リフレクション)を使い、対象を探す。
2. Sessar.NET を使う
3. Spring.NET を使う
4.Managed Extensibility Framework (MEF) を使う
実は、1.の方法は、私も以前に実装し、稼働させたコードがあります。
2. 3. の方法は、オープンソースとして利用する方法ですが、
内部動作は、1の方法と同じで、設定などが定義しやすい作りになっています。
4. のMEF は、.NET Framework 4.0 より利用できるMS純正コンポーネントです。
MEFは、クラスコンストラクタのインジェクション以外にも、
プロパティ追加などの注入もできてしまう優れものです。
他、MSコンポーネントと言うこともありますが、使い勝手も分かりやすい面もあるので、
ここでは、4. の Managed Extensibility Frameworkの簡単な方法を示します。
まずは、インターフェース (HelloInterface.DLL として作成)
public interface IHello
{
string GetMessage();
}
次に実装クラス (HelloImpl.DLL として作成)
using System.ComponentModel.Composition;
..
[Export("Hello", typeof(IHello))]
public class Hello : IHello
{
public string GetMessage()
{
return "Hello";
}
}
コンテナー側
using System.Reflection;
using System.ComponentModel.Composition.Hosting;
..
public class MefContainer
{
private static T getInjectionObject<T>(string contractName, AggregateCatalog catalog)
{
using (CompositionContainer container = new CompositionContainer(catalog))
{
return container.GetExportedValue<T>(contractName);
}
}
public static T GetComponent<T>(string contractName = null)
{
using (AggregateCatalog catalog = new AggregateCatalog())
{
catalog.Catalogs.Add(new DirectoryCatalog("./bin"));
return getInjectionObject<T>(contractName, catalog);
}
}
}
./bin を指定しているのは、Webアプリケーションを想定しています。
次に呼び出し側Webアプリケーション(ASP.NET MVC コントローラ)
public class helloController : Controller
{
public ActionResult index()
{
IHello hello;
hello = MefContainer.GetComponent<IHello>("Hello");
ViewData["Message"] = hello.GetMessage();
return View();
}
}
Webアプリケーション側の参照は、HelloInterface.DLLだけで良いのですが、
binディレクトリには、HelloImpl.DLLが無いと実行時にエラーが出ます。
これを実行すると、画面には、HelloInterface.DLLの実装部の"Hello"が表示されます。
ここから更に、別の実装クラス (HelloNewImpl.DLL として作成)
using System.ComponentModel.Composition;
..
[Export("HelloNew", typeof(IHello))]
public class HelloNew : IHello
{
public string GetMessage()
{
return "Happy new Year";
}
}
HelloNewImpl.DLLをWebアプリケーションの bin に配置
Webアプリケーション側のコードを、以下に変更
hello = MefContainer.GetComponent<IHello>("HelloNew");
実行すると、今度は、"Happy new Year"と表示jされます。
具体的な動作は、実装クラスに付く、Export属性に付けた コントラクト名と、
CompositionContainer の GetExportedValueによって、
対象コントラクト名を持つクラスがロードされてインスタンス化されているという事です。
この動作の現実的な利点は何かというと、
DLLファイルの置き換えによって、動作を変更できる点です。
上記の例では、"HelloNew"というコントラクト名を作ったもの作成していますが、
同一のコントラクト名を持てば、利用側のコードを修正せずに動作を変更できます。
リリース後に、不具合発生。リリース前に戻すという流れを、DLLの置き換えだけですむようになります。
また、コントラクト名は、文字列というのも大きな点です。
コントラクト名を羅列した定義ファイルをWeb.Config等に持たせれば、
動作変更を設定ファイルの変更と、修正DLLのコピーで済むようになり、
本体アプリケーションの再ビルドは不要になります。
これがDIコンテナーの一番の利点ですね。
Managed Extensibility Framework には、プロパティの注入という、
ある意味恐ろしい?!機能がありますが、
また、別の機会に書きたいと思います。
:関根