WindowsFormsアプリケーションにおけるDI戦略(まとめ)

ここ一ヶ月位、WinwodowsFormsアプリケーション上でDIコンテナを使う際に考慮しなくてはならない点に関して言及してきたが、やることはやった感がある。そこで、このテーマの最後として、何を行ったかをまとめておこうと思う。

.NETの場合、オブジェクトに対するメッセージのインターセプションとして一番手軽で確実なのは、RealProxyを使うことだが、AOPの実装として使うには制限がある。それをCastleProjectのDynamicProxyを使って打ち破ろう、という案。Aspectを織り込んだ際の実行時のオーバヘッドの削減に寄与したが、クラスの場合Virtualメソッドのみがインターセプト対象になる、動的に型の生成を行うので型の生成自体にオーバヘッドがある、という弱点もあることが判った。

DIコンテナを初期化する処理には、設定ファイルを読み込む処理と、実際に設定ファイルに宣言してあるコンポーネントの生成や依存性の解決までを行う処理が必要。これらをいつも行うのではなく、特にコンポーネントの生成に関してはできるだけ遅らせることによって、見かけ上の性能を上げることができる。

DIコンテナの便利な機能であるコンポーネントの自動登録機能、自動バインド機能がアプリケーションの起動時間にどれだけのパフォーマンスヒットを及ぼすかを検証。やはり便利な機能はそれなりに負荷も高いことが判明。起動時間を短縮するにはできるだけ明示的な設定の記述を行ったほうが良い結果が得られる。

.NETのDIコンテナはOGNLの優れた実装が無いため、式評価にはJScriptによる式評価に頼らざるを得ないところがあったが、Windowsが提供する新しいシェルであるPowerShellは.NETと統合されており.NETの機能が制限無く使えるので、これを式評価エンジンとして利用したらどうかという提案。Windows PowerShell RC1を実際に使い、式評価エンジンとして試してみたが十分に実用になることが判った。ただし、式評価にはやはりある程度の負荷がかかること、Windows PowerShellがまだRC1で時期尚早であることから、本採用は見送り。加えて、.NETの型コンバータによる変換以外の式評価はしない、ヌル実装の使用も考慮する。

.NETにおけるDIコンテナ+AOPの用途として、カスタムオブジェクトとWindowsFormsコントロールとのデータバインディングの自動化を試してみる。

DIコンテナにおいて最も多用される処理の一つがリフレクションである。リフレクションは多用されると共にJavaプラットホームでは負荷が高い処理のため、Seasar2は性能向上のためにリフレクション情報をキャッシュしているが、.NETのDIコンテナでも同様にキャッシュすることで性能の向上を望めるかを検証した。.NETは型の情報をメタデータとしてモジュールに内臓するため、Java程にはキャッシュの効果は無かった。

こんなところだろうか。元々開発対象としていた拙作のプロトタイプアプリケーションの起動時間だが、諸々の対策により、

     起動時間(※)
                                    • -
対策前 : 3953ms 対策後 : 2765ms ※DIコンテナの全ての初期化を完了して、メインフォームが表示されるまでの時間。

と、起動時間を1秒以上短縮することに成功していた。1秒といえば大したことが無いと思うだろうが、C/S系のプログラマであれば、データベース接続を含まないアプリケーションの起動時間を、1秒短縮することがやっかいな仕事であることは判るだろう。あと、短縮したといっても2.7秒じゃ実用に耐えないと思うかもしれないが、これはデバッグ時の数値であり、デバッグ情報の無い&Visual Studioホスティングされない、単独での起動時間はこの時間の80%〜90%程度に抑えられる。
起動時間短縮の要因だが、自身がDIコンテナを理解し習熟したことが大きいことは言うまでも無いが、やはり基本通りに、「今行う必要の無い処理は後で行う」「まとめて行うことのできる処理はまとめる」「最も処理負荷が高い部分を改善する」などが相乗して効いているのだろう。
なお、エントリとして言及していないと思うが、コンポーネント(オブジェクト)をDIコンテナで生成するか、アプリケーション内部で生成するかの判断は非常に大切だと思う。例えば、Webサイトのクライアントとして、任意のサイトと通信を行うコンポーネントと、接続先のエンドポイントを抽象化したコンポーネントがあったとして、設定ファイルでは以下のように宣言しているとしよう。


    connection
 -->

    true
    100000
    "MS932"
    "http://hogehost:9999/hogeApp/HogeAction"

コンポーネント"webclient"はコンストラクタインジェクションにより、その接続エンドポイントコンポーネントである"connection"を注入してもらう。アプリケーションでは"webclient"をDIコンテナから取得するだけであり

IDIContainer container = DIContainer.Create().Init();
IWebClient client = container.GetComponent("webclient");

これで"connection"注入済みの"webclient"が取得できるし、DIコンテナ便利だねぇなのだが、仮にアプリケーションの仕様としてHTTP通信を使用することが絶対だった場合に、少しでも起動時間を短縮したいとしよう。その場合"webclient"をわざわざDIコンテナに宣言する必要は無いかもしれない。その場合は"webClient"の宣言は削除するが、接続先のエンドポイントが環境によって変わる可能性があるので、"connection"はDIコンテナで管理する。結果、設定ファイルとコードは以下のように変化するだろう。
"webClient"を削った設定ファイル


    true
    100000
    "MS932"
    "http://hogehost:9999/hogeApp/HogeAction"

"webClient"はアプリケーション側で生成する

IDIContainer container = DIContainer.Create().Init();
IWebConnection con = container.GetComponent("connection");
IWebClient client = new WebClientImpl(con);

むろんこれは例えだし、このような構成にすると、DIコンテナのメリットの一つである柔軟な構成とテスタビリティは下がるだろう。しかしこの場合、テスタビリティ重視か、効率・性能重視かを実運用時に選択できる、ということが重要なのである。