しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

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

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

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

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

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

いろんなところで何回も言ってるとは思いますが、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 に渡す形になるので、結合がちょっと強すぎる感じです。

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

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

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

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

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

アクションでは 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 は採用していません。

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 だと思います、多分…。