Hatena::ブログ(Diary)

まめしば雑記 Twitter

2011-12-31

年末だからこそ ASP.NET MVC のモデルの作り方について考えてみる

Project Silk とか EFMVC とか Maintainable MVC Series を読んでると、どれもモデルが非常に厚いんですよね。 よくサンプルコードであるような EF の DB Model を直接 View に流し込むような作り方は使われていないわけですよ。

そして、今朝 MVVM Framework の Livet 作者で MVVM パターンのプロフェッショナルである尾上さんが ASP.NET MVC を学んでいるとの情報が入ってきました。

はてな、ちゃんと URL 展開汁。

まあ、それはいいとして。自分としてもモデルの作り方で悩んでた部分があるので、これを機にちょっとまとめてみようかと思いました。年末に昼まで寝てゲーム三昧とか人間としてダメすぎるので。

とりあえず ASP.NET MVC の M,V,C について図を作りました。矢印の向きに関しては SmartArt の使い方がよくわからなかったのでおかしい点があります。

f:id:shiba-yan:20111230222522p:image:w400

いろんなところで何回も言ってるとは思いますが、ASP.NET MVC では M のサポートはありません。System.Web.Mvc に含まれているのは Controller と View 周りだけです。

ということはモデル部分は Entity Framework でも NHibernate でも自由に選択してくれというスタンスです。実際のところは .NET Framework 4 に含まれている LINQ to SQL か NuGet などで単体配布されている Entity Framework 4.1 になるかと思いますが、基本的には両方とも ORM なのでモデルの重要な部分は自分で作る必要が出てくるわけです。

MSDN マガジンの ドメイン モデル パターンを使用する に出てくる図が分かりやすいかもしれません。

まずは現状の ASP.NET MVC でよく使われている構造と EFMVC, Project Silk の構造に関してまとめてみました。自分の理解が間違っているところはあるかも・・・。

スキャフォールディングの場合

ASP.NET MVC 3 Tools Update のスキャフォールディングでは、コントローラのメンバとして DbContext を持つようなコードが生成されます。モデルは非常に薄いですし、DB Entities を View に渡す形になるので、結合がちょっと強すぎる感じです。

f:id:shiba-yan:20111231011303p:image:h300

アクションに直接 LINQ でデータを取得するコードを書いたりしますので、テスタビリティを考えると非常によろしくないです。小規模のサービスならこの程度でもいいかもしれませんが…。

リポジトリパターンの場合

最近のサンプルコードではリポジトリパターンを適用しているものが多いです。私が書いた ASP.NET MVC 3 開発入門 でもリポジトリパターンを使って実装しています。

少しだけモデルは厚くなりましたが、実際のところは DbContext と LINQ でのクエリを隠蔽をしているだけです。

f:id:shiba-yan:20111231025748p:image:h350

DbContext を直接持たずに、コントローラのコンストラクタでリポジトリのインスタンスを受け取るのが一般的です。サンプルコードとしては以下の記事を参照してください。

ASP.NET MVC 3 開発入門 (4) - リポジトリパターンを適用する - まめしば雑記

ASP.NET MVC 3 開発入門 (6) - コントローラの実装 - まめしば雑記

EFMVC の場合

リポジトリパターンを単純に適用した場合よりもドメインモデルが増えた分だけモデルは厚くなっています。特徴的なのは Command Query Responsibility Segregation (CQRS) をドメインモデルに使っていることです。*1

CQRS については MSDN マガジンに記事があります。 MSDN マガジン: Windows Azure 開発 - Windows Azure での CQRS

f:id:shiba-yan:20111231120517p:image:h460

アクションでは Command を作成 -> CommandBus でコマンド実行という処理を行っています。少しコードを引用してみます。

var command = new CreateOrUpdateCategoryCommand(form.CategoryId, form.Name, form.Description);
IEnumerable<ValidationResult> errors = commandBus.Validate(command);
ModelState.AddModelErrors(errors);
if (ModelState.IsValid)
{
    var result = commandBus.Submit(command);
    if (result.Success) return RedirectToAction("Index");
}

コマンドを経由することで、モデル周りが結構綺麗ですね。ちなみに CommandBus は Dependency Injection を使って、コンストラクタで受け取るようになってます。

CommandBus では Submit されたコマンドに対応する Handler を DI で取得して実行します。

Project Silk の場合

Project Silk も EFMVC のようにドメインモデルがあるためモデルは厚くなっていますが、Commands は採用していません。

f:id:shiba-yan:20111231120103p:image:h460

EFMVC と同様に Project Silk でも全体的に Dependency Injection を使っています。アクションでは Using メソッドを使い、Handlers のインスタンスを取得して呼び出す形になっています。

var vehicles = Using<GetVehicleListForUser>()
    .Execute(CurrentUserId);

AddYearMakeModelSelectListsToViewBag();

var vm = new VehicleAddViewModel
             {
                 User = CurrentUser,
                 Vehicle = new VehicleFormModel(),
                 VehiclesList = new VehicleListViewModel(vehicles) { IsCollapsed = true },
             };
vm.VehiclesList.IsCollapsed = true;

return View(vm);

Project Silk の Handlers は EFMVC の Commands / Handlers を合わせたものと思ってもらって問題ないと思います。こっちの方がシンプルな作りですね。

基本的に EFMVC と Project Silk ではビジネスロジックを全力でドメインモデルに入れるようにしています。まあドメインモデルなので当然なのですが、アクションでの処理は最小限となっているので、モデルとは逆に薄くなっています。

このようにモデル部分の作り方に関してはいろいろな考え方があり、実際はケースバイケースで使っていけばいいと思うのですが、一般的なサンプルコードでは頑張ってリポジトリパターンという状況です。もうちょっとドメインモデルのことも考えていきたいです。

まずは自分が勉強という・・・。今も勉強中・・・。

*1:CQRS だと思います、多分…。

ugaya40ugaya40 2011/12/31 19:25 とりあえず所感を。


スキャフォールディングとEFの組み合わせををシンプルに使うのはMSさんいつもの大人の事情(マーケティング上の事情)だと思っています。デモとしてものすごく簡単にできるように見えるから映えるというだけで。実際はごく限定的なシナリオでしか適用できないもので。



数日前もつぶやきましたが、やはり僕もModelはもっと厚くします。そうでないとControllerがControllerの責務を超えて多くの責務を処理することになりがちです。

ControllerでDBのテーブル単位のエンティティをLINQで組み合わせて云々はそれは絶対にControllerの責務ではなくModelの責務です。

しかしそれは決していつもEFとスキャフォールディングの組み合わせで実装できないというわけではなく、
例えばそのアプリケーションではDBアクセスしかしない、基本的にビジネスロジックはストアドプロシージャの中で行われる。
ストアドプロシージャの出力は各画面での表示項目にきちんと対応する、といった設計が行われている場合、
EFのエンティティはストアドのINとOUTのマッパーとなり、その場合はスキャフォールディングをシンプルに使用でき、コードでのModel層(多くのModelの実体はストアドプロシージャの中にある)は非常に薄くなりデモに近い形で実装できると思います。

しかしその他のリソース(ファイルアクセスや外部システム連携)へのアクセスが行われる場合、シンプルにスキャフォールディングが使えるModelが出来るとは思っておりません。きちんとModel層としてラップし、理想的にはユーザーから見たユースケースレベルの粒度のインターフェースこそがContollerに公開されるべきものだと思っています。

大きなシステムになればなるほど、シンプルなDBアクセス以外の処理がないという事はめったになくなってきますし、また開発にたずさわる人数が増えていくにしたがって全てをストアドプロシージャに閉じ込めるような設計は現実的ではなくなってきます。

僕としては当然厚いモデルに賛成です。しかし、この記事でいうようなドメインモデルはちょっと広い意味で使われすぎていてかえってわかりにくくなっていると思います。実際はドメインモデルパターンの対義語のように使われるトランザクションスクリプトによるドメインロジックの実装も極めて有用に機能する事が多いと思っています。



MSさんのここら辺の大人の事情はきっと内部でもいろいろあるんでしょう。以前こんなことがありました。
もともとASP.NET MVC以前にEFの使われ方自体がMSさんのデモのシンプルな形では多くの場合悲劇です。

http://japan.zdnet.com/cio/sp_07microsoft/20376043/

EFは結局ただのDB直データセット・TableAdapterでは終わらずにドメイン主体のアプローチをきちんと取れるようになっていると聞いています。しかしデモでの見栄えの良さからか、多くの場合以前のDB直TableAdapterの発展版のように(データ中心?)で扱われる事が多いです。またそういうに思わせる形でVSのメニュー項目や機能名がつけられる事が多いと思っています。おそらくはMS内部でそういう圧力があるんでしょうね。

ASP.NET MVCのデモも多くの場合それに引きずられているだけだと僕は思っています。

しばやんときちんと声でこの件お話したいところです。大変素敵な記事だと思います。日本でこういった議論は少ないので盛り上がっていくといいですね!

妙なタイミングですが、今年は本当にお世話になりました。何度かお会いしてお話できて本当に光栄でした。
できたら来年もよろしくお願いいたします。

shiba-yanshiba-yan 2011/12/31 19:51 正直なところ自分はモデル周り結構適当にやってきてたので、圧倒的に知識不足なんですよねー・・・。

とりあえず、デフォルトのスキャフォールディングをそのまま使えるのはデモレベル、頑張ってプロトタイピングぐらいだと思います。しかし MS 的には Ruby on Rails がフルスキャフォールディングと Active Record で簡単にサービスが作れることをアピールしているので、対抗として用意せざるを得なかったのだと思います。最近は Azure などの動きを見ても OSS の取り込みを重要視している感じなので。

そして、ここまでモデルなど厳密に区切って考えているのは .NET か Java ぐらいではないかと思うんですよね。今の Web の流れが LL 系に偏っている以上、素早く作れる方向に偏ってしまうのはある意味仕方ないのかなとも思っていたり。

ドメインモデルに関しては完全に自分の勉強不足です!ぜひ今度、直接会ってお話ししましょう!

ugaya40ugaya40 2012/01/01 08:07 うわー今読み直したら、つけたはずの敬称「さん」が抜けていました><。すいません><。心の中ではついています><

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

トラックバック - http://d.hatena.ne.jp/shiba-yan/20111231/1325304627