.NET開発者中心 厳選ブログ記事

MVVMパターンの常識 ― 「M」「V」「VM」の役割とは?

尾上 雅則
2011/05/18

Model

 Modelは、C#などの汎用プログラミング言語で記述され、ドメイン・ロジックとデータ、つまりはビジネス・ロジックとビジネス・ドメインのステートを持ちます。

Modelについてよくある誤解 ― ステートレス、サーバ側がModel、永続化層など

 Webシステムでよく採用される3層構造におけるデータ・アクセス層やビジネス・ロジック層の常識を、MVVMパターンのModelに持ち込むのはやめましょう。WPF/Silverlightなどのリッチ・クライアント世界のModelは、Webシステムのそれとは決定的に違います。決定的に違うのは、それはステートフルであることです。

 多くのWebシステムのビジネス・ロジック層やデータ・アクセス層自体は、基本的にステートを持ちません。なぜならWebシステムは、リクエストごとにレスポンスを返すのがすべての処理の基本となっているからです。リクエストを受けて、次のリクエストを受けるまでの間に、保持しておかなければならない情報は基本的にありません。

 しかしMVVMパターンでのModelはステートフルです。リッチ・クライアントは基本的に状態を持つものだからです。もしModelがステートレスだとしたら、どうなるでしょうか? ビジネス・ドメインのステートはどこが受け持つのでしょうか? ViewModelが受け持つのでしょうか? もしそうした場合、MVVMパターンの本来の目的である「ドメイン・ロジックとプレゼンテーション・ロジックの分離」はかなわなくなるでしょう。そんな実装では、そのMVVMパターンもどきである実装から何の意義もくみ取れなくなってしまいます。それはアンチパターンです。

 また、「Modelをサーバ側と見なす」という話もたびたび見かけます。「クライアント側はViewModelから」というお話です。ではクライアント側にビジネス・ドメインのステート管理は含まれないのでしょうか? 基本的に状態を持つリッチ・クライアントでそんな設計があり得るのでしょうか?。

 それはやはりアンチパターンなのです。サーバ側に何があろうと、基本的にリッチ・クライアント側にもビジネス・ドメインの情報は存在するはずです。アプリケーションが通信するサーバや、外部サービスをModelと見なすのはやめましょう。クライアント側にしっかりModel層を作りましょう。

 ただ、Model層をWCF RIAサービスなどがある程度代替してくれることもあるようです。しかしそれはあくまでも、WCF RIAサービスがModelの代替をしてくれるわけであって、Model層がクライアント側に存在しないわけではないのです。WCF RIAサービスを使用していても、それとは別に、ビジネス・ドメインに属する情報を管理したり、ビジネス・ロジックを置いたりする場合は、WCF RIAサービスが生成してくれるプロキシ層をラップする形でModel層を置くのが適切です。

 「Modelを何層かに分ける」という話も聞きます。そんなの大抵の場合、当たり前です。Modelを何層に分けたって、それはModelには変わりありません。何らMVVMパターンから逸脱しているものではありません。

 どこかで、「MVVMパターンには、Modelと別に永続化層を設けるべきだ」という議論を見ましたが、そう考えるものではありません。永続化が機能要件であるなら、Model内のどこかに置けばよいのです。例えばWindow Phone 7上でのSilverlightアプリケーションで、「要件によらずViewModelに永続化層が必要だ」と思ったのなら、Windows Phone 7用MVVMパターン・インフラストラクチャとして用意すればよいのです。要件によらない機能はインフラストラクチャとして用意されるのが適切です。そういう考え方じゃないと、「ViewとViewModelの間にデータ・バインディング層がある」などという話になってしまいます。

Modelの持つべき実装

 上で述べたように、Modelはステートフルであるべきです。Modelがステートフルであれば、Model操作の結果はModelのステートのプロパティ変更という結果をもたらす場合が増えます。ViewModelがModel機能を非同期で呼び出した場合を考えてみましょう。その結果は「Modelプロパティの更新」という形なのですから、Modelに変更通知機能がなければ、ViewModelが結果を取得するすべはありません。

 従ってModelは変更通知機能を持ちます。ViewModelと同じように、INotifyPropertyChangedインターフェイスの実装として行われます。

 また、Modelは入力値検証機能も持ちます。例えばRDBMSを使用していれば、データベースに格納可能な値は事前に決まっています。UIがなくとも格納可能な値は決まっていることが多いでしょう。そういった入力値の検証はModelで行います。ViewModelは、それをそのままViewに伝えればよいのです。IDataErrorInfoインターフェイスを実装するのですが、素直に実装するとエラー表示用文字列がModelに含まれてしまいます。リソース・ファイルを使用して、リソース・キーだけをModelの実装に含めるのを推奨します。

ドメイン・ロジック・パターン

 前述のとおりModelはステートフルですので、Modelエンティティの変更をViewModelに伝える必要があります。そしてその方法はイベントです。これを考慮したうえで、ViewModel側から見えるModelのインターフェイスはどういった形であるべきでしょうか。そしてステートフルとはいっても、何の情報をModelに保持し、何をModelに持つべきではないのでしょうか。

 まず、「Modelがビジネス・ドメインに属するのが最大の前提である」ということを思い出しましょう。つまりModelはステートフルとは言っても、Modelが持つべき情報はビジネス・ドメインに属する情報で、表示専用の情報であってはいけないはずです。この前提が守られなければ、そもそもMVVMパターンの前提である、「プレゼンテーション・ロジックとドメイン・ロジックの分離」はかないません。

 この問題は当然、簡単には解決できません。そして、それは「どういったUIを作成するのか?」という問題と根強く密着しています。

 そこである程度は、Modelを設計するうえでの指針が欲しいのは事実です。そこでModelが扱うドメイン・ロジック、その設計パターンとして『PoEAA(Patterns of Enterprise Application Architecture)』に提示されているドメイン・ロジック・パターンを考えてみましょう。

 ドメイン・ロジック・パターンとしては、大きく下記の2つが挙げられます。

  • ビジネス・ドメインと深く密着し、エンティティをビジネス・ドメインの分析から定義し、エンティティに操作の責務を持たせるドメイン・モデル・パターン
  • ユース・ケース単位、あるいはユース・ケース・グループ単位でエンティティを切り、そのエンティティは表示状態と大きく乖離(かいり)しないトランザクションション・スクリプト・パターン

 ドメイン・ロジック・パターンとしては、ほかにもテーブル・モジュール・パターンというのがありますが、意義的に上記の2つの組み合わせた中間的なパターンですので、ここでは触れません。

 「日本のWebシステム系開発では、ほとんどの場合、トランザクション・スクリプト・パターンが用いられる」と聞きます。ユース・ケース単位にエンティティをスクラッチで作っていくということは、画面に表示する情報のためのエンティティをスクラッチで作っていくこととほとんど等しいからです。このアプローチは多くの場合で有用ですが、いくつか問題を抱えてもいます。

 よく挙げられる問題は、「重複する処理の共通化を行いにくい」ということです。実装者が見ているのはユース・ケース単体ですから、当然ほかのユース・ケースとの共通処理には気付きにくくなります。

 また、MVVMパターンのターゲットであるリッチ・クライアント開発という視点で見たときは別の問題も見えてきます。

 リッチ・クライアントのModelがステートフルであるのは前述したとおりです。そしてModelという一部分に限らず、リッチ・クライアントというのは基本的に状態を持っています。それが、1画面、1画面、使い捨てのWeb開発と、最も異なるところです。

 リッチ・クライアントのドメイン・ロジックを、トランザクション・スクリプト・パターンで実装していったとしましょう。保持しているエンティティの状態に変更があって、その変更を別のエンティティに伝えたい場合は、どうすればよいのでしょうか? 変更を通知されたエンティティが何か自身の状態を変化させなければならないとしたら、どうすればよいのでしょうか? ユース・ケース単体を見てエンティティを設計・開発しているので、前述した共通化処理の問題と同様に、すでに取得したエンティティ間に存在する、影響を与え合う関係を把握する手段がありません。トランザクション・スクリプト・パターンによるドメイン・ロジックの開発では、このようにステートフルなエンティティ間の通知と遷移の管理が難しくなってしまいます。

 だからこそのドメイン・モデル・パターンなのです。ドメイン・モデル・パターンでは、ビジネスを意味的に分析し、エンティティを定義します。エンティティが操作を持つことが重要です。エンティティが操作を持たないWebシステムにおける3層の常識は捨てましょう。Webシステムでエンティティが操作を持つことがないのは、そのエンティティは1回使い捨てだからです。エンティティの状態が変化する際には、元のエンティティは破棄され、新しく作り直されるのが普通だからです。

 そして意味的な分析から生まれたModelは、イベントを使ってお互いに通知し合います。この手法はViewModelとModelの間でのエンティティの変更通知方法と同一なので、統一できています。

 そしてイベントを使ってドメイン・モデル間が通信し合うことは、自身が状態を持ち、ほかのエンティティとの関連によって自身のステートが変化していく形と非常に相性がよいのです。トランザクション・スクリプト・パターンで見えていた問題はすべて解決します。

 ドメイン・モデルに属さないと思われる操作は、別途、サービス層を作って対処します。Webサービスなどのステートレスなサービス層と何が違うのでしょう。それはWebサービスのサービス層がすべてサービス・メソッドで操作するのに対して、ドメイン・モデルのサービス層は、あくまでもドメイン・モデルに含まれない操作だけを行うところです。ドメイン・モデルに対して行える操作は、極力、ドメイン・モデルで実装します。つまりドメイン・モデル・パターンでのサービス層はごく薄くなります。そしてサービス層が返すエンティティは、ユース・ケースから導出されたトランザクション・スクリプト的なエンティティで構いません。ドメイン・モデルに属さない操作なのだから、それでいいのです。ユース・ケースから導出されたエンティティは、必然的に画面構成に近くなります。

 リッチ・クライアントをリッチ・クライアントらしく作っていった場合は、ほぼ必ずエンティティ間の関連による変更が発生するはずです。ドメイン・モデル・パターンは、オブジェクト自身が変化していくパターンと相性がよいとされています。まさにリッチ・クライアントにうってつけのパターンです。

 トランザクション・スクリプト・パターンはシンプルですが、リッチ・クライアントでは、トランザクション・スクリプト的なパターンのみでは対処しきれなくなることも考えられます。

 いずれにせよ、Modelの内部は要件に合わせた設計が大切です。あまりにもドメイン・モデルに属するものが少なく、サービス層が分厚くなりそうならば、トランザクション・スクリプトで開発するのもよいでしょう。

 しかし、少なくとも日本でわたしがかかわった(いや日本でしか開発したことないですけど)、いわゆる「業務システム」といわれる種類のリッチ・クライアント開発では、Webシステムの画面をそのままリッチ・クライアントに移しただけのものがまだまだ圧倒的に多いのが現状です。各画面で行われた操作の結果が、すでに表示されているほかの画面の結果に影響をまったく与えなかったりします。要件がそういった形であるならば、トランザクション・スクリプト・パターンでの実装の方が適切である場合の方が多いのでしょう。

 しかし「RIA(リッチ・インターネット・アプリケーション)だ」「UX(ユーザー体験)だ」といわれる昨今、Webシステムの世界と違って、多くの場面で、リッチ・クライアントでのドメイン・モデル・パターンの重要性はより高くなると、わたしは確信しています。

 各責務の詳細と考え方は、これで以上になります。

 最後に、冒頭で簡単に述べた「MVVMパターンによるデザイナーとプログラマーの協業」について説明します。


 INDEX
  .NET開発者中心 厳選ブログ記事
  MVVMパターンの常識 ― 「M」「V」「VM」の役割とは?
    1.MVVMパターン概要
    2.View
    3.ViewModel
  4.Model
    5.デザイナーと開発者の分業/MVVMパターンを適用・カスタマイズするときの留意点

インデックス・ページヘ  「.NET開発者中心 厳選ブログ記事」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH