DIコンテナの選定理由について
今回、WPFアプリケーションではAutofacを、WCFサービスでSimpleInjectorを採用しています。本来であればこれらは、どちらかに統一すべきでしょう。
コンテナの機能だけを比較した場合、SimpleInjectorに旗が上がります。SimpleInjectorは後発であるため、作りが全体的にモダンであり、その結果動作が非常に高速です。そしてモダンなDIコンテナの中では、コミュニティが成熟しており今後のメンテナンスにも期待ができるように見えます。
詳細なDIコンテナの比較は、次の記事が参考になるでしょう。
そのためWPFもSimpleInjectorを選択したいところなのですが、現時点ではPrismがサポートしていません。
サポートしていないなら、自分でサポートを追加しても良いのですが、ちょうどタイミングが良くありません。
執筆中の2018年3月27日現在、Prismは7.0のリリースを間近に控えています。そして7.0ではDIコンテナ周りの実装が大きく変更になっています。現在リリースされているStableは6.3になりますが、それ向けにSimpleInjector対応を行うのは、すぐに無駄になる可能性が高いですし、7.0はまだDIコンテナ周りがフィックスしていないため、タイミングが早すぎます。
そのため、今回はAutofacを選択します。ただしPrism 7.0がリリースされたのちに、SimpleInjectorの採用をPrismコミュニティへ提案し、同意が得られるようであれば実装を提供しようと考えています。
もしPrismでSimpleInjectorが採用された場合には、DIコンテナの変更を検討したいと思います。なおDIコンテナの差し替え自体は、非常に容易に行うことが可能です。
Catsle.Coreの採用について
本システムではWCFサービスの認証の仕組みと、トランザクション制御にAspect Oriented Programing(AOP)を採用します。それらの機能は、WCFサービスで提供されるあらゆるAPIに横断的に適用する必要があります。こういった場合にAOPを採用することで、非常に簡潔かつ高品質な実装を行うことができます。
AOPの詳細を知りたい方は、以下の記事もご覧ください。Xamarin向けに書いた記事ですが、前半のAOPの解説についてはプラットフォームと関係ない内容になっています。
さてAOPを実装するにあたって、手段は複数ありますが、今回はCatsle.CoreのDynamicProxyを利用することとしました。理由は以下の通りです。
- .NET FrameworkのAOP実装として非常に多くのプロダクトで活用されている
- 動作が非常に軽快である
実装にはRefrection.Emitを利用した動的コード生成が利用されています。このため初回起動時にコンパイル処理が入りますが、以降は非常に高速に動作します。
Catsle.CoreとSimpleInjectorを組み合わせて利用する必要があるのですが、そのためには一工夫が必要になります。そこで簡単に利用するための外部ライブラリを作成しました。良かったらご利用ください。
代替手段として、静的コード生成を利用したCauldron.Interception.Fodyなどを利用する方法があります。こちらはコンパイル時にプロキシーが自動生成されるため、Catsle.Coreよりも高速に動作します。また静的にコード生成をしているため、例えばXamarin.iOSなどでも利用できる利点があります(Catsle.CoreはXamarin.iOSでは動作しません)。
しかしCauldron.Interception.Fodyは早いとはいえ、その理由はプロキシーの生成がコンパイル時に行われる点にあります。比較してCatsle.Coreは初回の実行時に動的にコード生成が行われるため、初回の処理が遅くなります。しかし今回の採用箇所はWCFサービスであり、一度起動した後は頻繁に再起動することは想定されません。したがってパフォーマンスによる差異は実質的にないと考えてよいでしょう。
また自由度の側面で見ると、Catsle.Coreに軍配が上がります。具体的にはCauldron.Interception.Fodyでは、メソッドの呼び出しを途中でブロックしたり、発生した例外をインターセプトしたりといった実装が不可能です。前者は例えばキャッシュの実装などが、後者はデッドロック時の自動リトライなどが考えられます。
こういった理由で、今回はCatsle.CoreのDynamicProxyを採用することに決定しました。
Cauldron.Interception.Fodyに興味をお持ちの方は、以下をご覧ください。こちらもXamarin向けの記事になっていますが、Cauldron.Interception.Fodyの利用方法自体はWPFでも完全に同じです。
SystemManagerとSystemManager.DatabaseAccessそれぞれの値オブジェクトについて
本システムでは、例えば管理対象の従業員を表すほとんど同じManagedEmployeeクラスが、SystemManagerコンポーネントとSystemManager.DatabaseAccessesコンポーネントの二か所に存在しています。
SystemManagerの値オブジェクトは、DatabaseAccesses以外の各レイヤーで共通クラスとして利用されます。SystemManager.DatabaseAccessesの値オブジェクトはデータベースのテーブルやビューと対になるクラスで、Dapperを通して利用します。
これらは多くの場合は全く同じプロパティを持ちます。
しかし、SystemManager.DatabaseAccesses側の値オブジェクトのプロパティは、場合によってはデータベースの製品に依存する型を取ることがあります。
またSystemManager側の値オブジェクトは複数種類のオブジェクトによるツリー構造をとることもありますが、SystemManager.DatabaseAccesses側のオブジェクトはそういった構造を取らないことが多いでしょう。
もちろん、ケースによっては全く同一のものしか発生しないこともあります。その場合は、一つだけ定義して共有すると良いでしょう。
しかし多くの場合は、無理をすれば共有化できないことはない。といった感じになります。その場合は、無理に統一することは必ずしも効率的になるとは言い切れません。
逆に、分けた場合であっても、増える手間はほとんど類似の値オブジェクトを二重で生成することと、値の詰め替えの手間が発生することくらいです。
前者については、今回はデータベースのテーブル構造から、Dapper.FastCRUD.ModelGeneratorを利用して自動的にSystemManager.DatabaseAccesses側のオブジェクトを生成するため、SystemManager側のクラスはそこからコピー&ペーストで生成すれば大きな手間は発生しないと考えています。
Dapper.FastCrud.ModelGeneratorについては、以下の記事もご覧ください。
後者、つまり値の詰め替え処理の手間については、AutoMapperライブラリを利用することで大幅に軽減されます。
例えば次のコードはSystemManager.DatabaseAccessのManagedEmployeeをSystemManagerのEmployeeに詰め替えているコードになります。
Mapper.Map<ManagedEmployee>(managedEmployee);
詰め替えの実装は大きなコストになりませんし、分離することで安定した構造を手に入れることができます。
このため本システムでは、SystemManagerとSystemManager.DatabaseAccessそれぞれの値オブジェクトを定義し、SystemManager.Service.Impleコンポーネントでオブジェクトの詰め替えを行うこととします。
Entity FrameworkではなくDapperを利用する理由
これはよく議論されている話題でもあります。私がDapperというかMicro ORMを利用する最大の理由は、Entity FrameworkのLINQでは記述できないSQLの実行が、エンタープライズアプリケーションでは避けられるとは限らない点にあります。
例えば特定条件による複数行のアップデートや削除などがそれに該当します。
もちろんEntity Frameworkでも生SQLの実行と同居することは可能です。しかしEntity Frameworkを通した処理で実行されるSQLは、ユーザー側で実行順序が制御できないという問題があります。例えば、
- Entity Frameworkで処理Aを実行
- 処理Aが実行済みの前提で、生SQLで処理Bを実行
みたいな処理をしたくとも、実際には実行順序が逆になってしまうといったことが起こります。この実行順序はEntity Frameworkでは制御できません。
また、Entity FrameworkのLINQは非常に便利なのですが、パフォーマンスを気にする場合に、LINQがどういったSQLを生成するか「忖度」してあげないといけない、なんてことも有りがちです。そもそもEntity Frameworkは、Micro ORMと比較して速いとは言えないという側面もあります。
業務アプリケーションの開発現場では、SQL(特にDML)を記述することは苦にならないというエンジニアが一般的でもありますので、エンタープライズアプリケーション開発の現場では、Micro ORMを利用したいというのが私の実情です。
その上で、どのMicro ORMを利用するか?となった場合、経験者の確保や情報収集の容易性を考慮すると、.NET Frameworkでは現在Dapperがデファクトスタンダードであり、多くの場合において適切な選択だと考えています。
Dapper拡張ライブラリの選定
さてDapperを利用するとした場合、素のDapperだけでは開発生産性が高くない(例えばPrimary Keyを指定して1行取得したいといった場合でもSQLをフルに書かないといけない)ため、拡張ライブラリの併用が好ましいです。拡張ライブラリを利用することで、Entity FrameworkのLINQとは言わないまでも、利用頻度の高いSQLはコードのみで表現することも可能になります。
Dapperの拡張ライブラリは多くあります。今回はDapper.FastCRUDを採用しました。DapperExtensionsなども選択肢としてはありだと思います。
詳細な比較は、以下の記事にも記載していますので良かったら、併せてごらんください。