EFMVC より、コントローラのPOSTアクションメソッドの流れを追う
コントローラのPOSTアクションメソッドで、とても綺麗だな〜と思ったコードがありましたので、それについて書きます。
GETなアクションメソッドはまた別の話です。
そのコードは EFMVC で書かれているものです。
綺麗だな〜と思ったコード
コードはこれです。(EFMVCのコードを少し変更したものです。)
/// コンストラクタ public CategoryController(ICommandBus commandBus) { this.commandBus = commandBus; } [HttpPost] public ActionResult Save(CategoryFormModel form) { if(!ModelState.IsValid) return View(form); //作成コマンド var command = new CreateOrUpdateCategoryCommand(form.CategoryId,form.Name, form.Description); //検証 IEnumerable<ValidationResult> errors = commandBus.Validate(command); ModelState.AddModelErrors(errors); if (!ModelState.IsValid) return View(form); //実行 commandBus.Submit(command); return RedirectToAction("Index"); }
注目する点は、
コントローラーは、ICommandBusと、リクエストする内容、この2つだけを知っているだけでいい
ということです。
WEBアプリにおいてデータのコミットで重要なタスクとなるのは、
- 検証
- 実行
- 次のページへ
この3つです。
これら3つの作業を、コントローラはICommandBusと、リクエストの2つだけで行っています。
コントローラが扱うものが少なくなると、テストの記述が簡単になりますし、
また、コードから何をしているのか確認しやすくなるという利点があります。
ICommandBusは全アクションメソッドで使えます。コピペ…コードの自動生成も楽ちんです。
そして、インターフェイスであり、名前の通り、コマンドのベルトコンベアみたいな役割を持っています。
つまり、コントローラは何をしているのかというと、
アクションメソッドに合うリクエストを作って、後はそのベルトコンベアにポイって投げているだけです。
ICommandBusがどのように実装されていて、ベルトコンベアがどこにいって、誰が実行しているのか、
モデルの設計がどのようになっているか、記載していきます。
コードの場所
コードは EFMVC からDownloadできます。
順番に追っていきます
先ほどのコードを例に追っていきます。
コマンドの作成
[HttpPost] public ActionResult Save(CategoryFormModel form) { if(!ModelState.IsValid) return View(form); var command = new CreateOrUpdateCategoryCommand(form.CategoryId,form.Name, form.Description);
実行するためのコマンドクラスを作成しています。
フォーム送信データ → 実行コマンド へのマッピングみたいなものなので、Automapperで行うのもありだと思います。
検証
IEnumerable<ValidationResult> errors = commandBus.Validate(command);
ICommandBusのValidateメソッドにコマンドを渡し、検証結果を受け取っています。
(このValidationResultは、EFMVC.Coreプロジェクトにある検証結果をもつクラスですが、
DataAnnotationのValidationResultでも良いと思います。)
ModelState.AddModelErrors(errors);
そしてその検証結果を、拡張メソッドを利用してModelStateに追加しています。
これも、モデルの検証結果 → コントローラのModelState というマッピングの1つだと思います。
検証の流れは以上です。
次は、実際に実装している部分を追います。
ICommandBusと検証の実装クラス
ICommandBusのValidateメソッドの呼び出し先は、
ICommandBusの実装クラス、EFMVC.CommandProcessor.Dispatcher.DefaultCommandBusクラスです。
DefaultCommandBusでは、指定された型に合う実装クラスを取得&実行します。
つまり、渡されたリクエストに合う、検証クラスを呼び出して実行します。
Validateメソッドが呼ばれた時に、
MVCのDependencyResolverから取得します。取得されるクラスは、今の場合は
MVCのDependencyResolverから、実装クラスが取得できるようにするためには、
事前にIoCフレームワークのAutofacを使って、独自DependencyResolverをMVCに登録する必要があります。
場所は、EFMVC.WebプロジェクトのBootstrapperクラスです。
Interfaceと実装クラスのマッピングを設定した後の1番最後にある、
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
これです。(最近こればっかり書いているような・・・)
DefaultCommandBusクラスは、EFMVCではMVCのDependencyResolverを利用して実装していますが、
DependencyResolverを使わなくても実装できます。
UnityのIServiceLocatorを利用したり、自分でインスタンスを取得したり、自由に実装できます。要は具象クラスを呼び出せられればOKです。
私の場合はStructuremapを利用しているので
T handler = ObjectFactory.GetInstance<T>();
と書いています。
実行
commandBus.Submit(command);
実行も、検証の流れとほぼ同じです。
ICommandBusのSubmitメソッドにコマンドを渡しています。
(Submitメソッドは、boolを返していますが、意味がないのでvoidが良いと思います。)
検証は
実行の時は、
実行後
実行後は、return View();でページ遷移です。
POSTアクションメソッドの流れは以上になります。
コントローラのPOSTアクションメソッドについて
以上、EFMVC より、POSTアクションメソッドの流れを追ってみました。
今一番試してみたい設計ですが、クラスの数が大量にならないかな〜という心配があります・・・。
でも、検証&実行は頻繁に使う流れなので、ICommandBusでまとめたいところです。
1つのリクエストを検証メソッドと実行メソッドの2つに通す、というのも綺麗です。
コントローラのPOSTのモデルは、そんなに複雑にならないので、そんなに凝らなくても大丈夫かな〜と思いますが…。
(検証ロジックと実行ロジックがまとまっていれば!)
問題は、GETです。
ViewModelの組み立てが大変で、現状問題ありありです。解決策はまだ見つかっておりません(+_+)
おまけ CQRSパターンについて
EFMVCは、CQRSパターンを利用しています。
”クエリ(取得)”と”コマンド(実行)”を、それぞれ独立した責務として明確にモデル化するというものです。
なので、取得はRepositoryに、コマンドはHandlerと別々になっています。
CQRSパターンについては
Windows Azure での CQRS
Greg Young流CQRS - Mark Nijhof
副作用を最小限に抑えるために必要なこと
に説明が載っています。(難しくてわからなかった…)