はじめに
『システム構築の現場にもっと「易しさ」と「優しさ」を』というコンセプトのもとに開発が進められている「Seasar2」というオープンソースをご存知でしょうか。「Seasar2」はDIコンテナとAOPという便利な機能を提供するJavaのフレームワークで、今後の開発の基盤として注目を集めています。本稿では「Seasar2」を.NET環境に移植した「S2Container.NET(以降s2dotnet)」と呼ばれるプロダクトを紹介します。
対象読者
.NETにて開発を行っている方で、設計やフレームワークなどに興味を持っている方を対象とします。
必要な環境
サンプルはVisual Studio .NET 2003で作成され、.NET Framework 1.1、s2dotnet 1.0.0で動作確認をしています。
s2dotnetとは
「s2dotnet」は、杉本和也氏と佐藤太一氏が中心となって「Seasar2」を.NET環境に移植したフレームワークです。s2dotnetを使用すると、DIコンテナとAOPの機能を、.NET環境で簡単に利用することができます。
DI(Dependency Injection)コンテナとは
「DI(Dependency Injection)」とは、インターフェースと実装を分離して、オブジェクトを外部から生成し設定する仕組みです。インターフェースと実装を分離するという考え方は以前からありましたが、これまではソースコードのどこかに実装へ依存する記述が必要でした。しかしDIコンテナを用いると、設定ファイルに基づいて、実行時に依存関係(実装クラス)を注入することが可能になります。
AOP(Aspect Oriented Programming)とは
「AOP(アスペクト指向プログラミング)」とは、複数のクラスに適用される共通の処理を、外から織り込む考え方です。AOPを用いると、ログ出力やトランザクション管理のような冗長な処理を1箇所に記述し、その処理を複数のクラスの任意のメソッドに織り込むことができるようになります。
s2dotnetの入手とセットアップ
s2dotnetは、s2dotnetのWebサイトの「ダウンロード」のリンクから入手します。執筆時点での最新バージョンは1.0.0です。
ダウンロードしたファイルを解凍した「Release」フォルダにs2dotnet本体の「Seasar.dll」があります。Visual Studioの場合、ソリューションエクスプローラの[参照設定]を右クリックして、[参照の追加]から「Seasar.dll」を選択します。その際、リソースファイルの「ja-JP」フォルダを「Seasar.dll」と同じフォルダに配置するようにしてください。またlog4netも必要になりますので「log4net.dll」を同じフォルダに配置してください。
s2dotnetを用いるメリット
インターフェースと実装を分離した設計
DIコンテナを用いて開発を行うには、インターフェースと実装を分離して設計することが重要になります。インターフェースと実装を分離すると、仕様の変更による影響を受けにくくなるというメリットがあります。
ここでは、RDBMSのアクセスに使用する「Connection」を例に見てみましょう。s2dotnetを活用すれば、RDBMSの変更があったとしてもソースコードを変更しなくても済むようになります。
以下のような、SQL Serverに接続するClass1
のHoge
メソッドがあったとします。
public void Hoge() string conStr = "Server=localhost;database=Northwind;" + "User ID=sa;Password=admin"; System.Data.SqlClient.SqlConnection Conn = new System.Data.SqlClient.SqlConnection(conStr); Conn.Open(); }
もし、RDBMSをSQL ServerからOLE DB(MDB)に変更することになった場合には、「conStr
」の接続文字列と「System.Data.SqlClient.SqlConnection
」の部分を「System.Data.OleDb.OleDbConnection
」へ書き換える必要があります。
public void Hoge() string conStr = "Provider=Microsoft.Jet.OLEDB.4.0;" + "User ID=Admin;Data Source=./s2dotnet.mdb"; System.Data.OleDb.OleDbConnection Conn = new System.Data.OleDb.OleDbConnection(conStr); Conn.Open(); }
そこで、インターフェースを使用するとプログラムの修正範囲を軽減することができます。この場合、変数の宣言部をSystem.Data.SqlClient.SqlConnection
とSystem.Data.OleDb.OleDbConnection
のインターフェースであるSystem.Data.IDbConnection
に変更します。
public void Hoge() string conStr = "Provider=Microsoft.Jet.OLEDB.4.0;" + "User ID=Admin;Data Source=./s2dotnet.mdb"; System.Data.IDbConnection Conn = new System.Data.OleDb.OleDbConnection(conStr); Conn.Open(); }
Conn.Open()
といったプログラム部は問題なく動作するため、変数Conn
をインターフェースに変更することは問題ありません。しかし、実装クラス(「new
」の後ろの部分)の変更時にはプログラムの修正が必要になります。そこで、s2dotnetを使用して実装クラスを外から設定するようにしてみましょう。
ここでは、実装クラスの記述を削除し、Conn
プロパティを作成します。
//プロパティ private System.Data.IDbConnection _conn; public System.Data.IDbConnection Conn { set{_conn = value;} } //処理 public void Hoge() _conn.Open(); }
続けて、s2dotnetの設定ファイルを記述します。
<!-- 「OleDbConnection」の定義 --> <component name="OleDbConn" class="System.Data.OleDb.OleDbConnection"> <!--コンストラクタの引数にConectionStringを設定してインスタンス化--> <arg>"Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin; Data Source=./s2dotnet.mdb"</arg> </component> <!-- 「Class1」の定義 --> <component name="Class1" class="S2dotnetSimpleSample.Class1"> <!--プロパティ「Conn」にOleDbConnを注入--> <property name="Conn">OleDbConn</property> </component>
s2dotnetからClass1
を取得するとConn
プロパティにSystem.Data.OleDb.OleDbConnection
が注入されるようになります。そのため、OracleやMySQLといった他のRDBMSを使用することになったとしても、実装クラスがIDbConnection
を継承していれば、設定ファイルを変更するだけで済みます。
もしかしたらプログラムが複雑になったと感じた読者もいらっしゃるかもしれません。しかし、これまでの開発では、必要とするクラスを取得するために実装クラスをハードコーディングするか、取得用のロジックを記述しなければいけませんでした。s2dotnetを用いると、コンストラクタやプロパティを経由して、必要とするクラスのインスタンスを自動で注入できるようになるので、むしろプログラムはシンプルになると言えるでしょう。
インターフェースと実装を分離して、DIコンテナによって注入するメリットを整理してみましょう。
- メンテナンス性の向上
- 品質の向上
- 開発期間の短縮
- 再利用性の向上
なお、実際の開発でデータベースに接続する時には、s2dotnetから提供されている「Seasar.Extension.ADO.IDataSource
」や後述する自動ローカルトランザクションを使用すると良いでしょう。
AOPを用いた設計
AOP(アスペクト指向プログラミング)とは、複数のクラスに適用される共通の処理を、外から織り込む考え方です。ここでは、ログの出力機能をAOPする例を見てみましょう。
<!-- 「TraceInterceptor」の定義 --> <component name="traceInterceptor" class="Seasar.Framework.Aop.Interceptors.TraceInterceptor" /> <!-- 「Class1」の定義 --> <component name="Class1" class="S2dotnetSimpleSample.Class1"> <aspect>traceInterceptor</aspect> </component>
s2dotnetの設定ファイルに「<aspect>traceInterceptor</aspect>
」という記述をすることにより、ログ出力処理がClass1
に織り込まれます。この結果、Class1
のメソッドが呼び出される前後に、以下のトレースログが出力されるようになります。
BEGIN S2dotnetSimpleSample.Class1#Hoge() END S2dotnetSimpleSample.Class1#Hoge() :
従来の開発では、対象クラスのメソッド全てにログ出力命令をプログラムする必要がありましたが、AOPを活用すれば設定ファイルを変更するだけで、様々な処理を複数のクラスに織り込むことが可能になります。また、データベースのトランザクションをAOPによって制御することも可能になります。「try~catch」命令を書かなくとも、トランザクション開始・コミット・ロールバックの制御ができるため大変便利です。
それでは、AOPのメリットを整理しましょう。
- 業務ロジックから冗長的な処理が排除されるため、シンプルなプログラムになります。
- システム的な処理を外出しして分離することで、メンテナンス性が向上します。
なお、s2dotnetでAOPを適用するには、System.MarshalByRefObject
の派生クラスであるか、 インターフェース型でクラスを受け取る必要がありますのでご注意ください。
なお、ここまでのプログラムのサンプルは「S2dotnetSimpleSample.zip」をご覧ください。