2011-06-27 12 views
7

私は、MVCの懸念の分離のための「適切な」構造は、あなたのビューを構造化し、選択したリポジトリに永続化するためのデータモデルを分離するためのビューモデルを持つことです。私はMongoDBを試し始めました。スキーマレスのNO-SQLスタイルのデータベースを使用する場合、これが当てはまらないと思っています。私はこのシナリオをstackoverflowコミュニティに提示し、皆の考えが何であるかを見たいと思っていました。私はMVCに新しいので、これは私には意味がありましたが、多分私は何かを見落としているかもしれません...MVCとNOSQL:ビューモデルをMongoDBに直接保存しますか?

これは私の例です:ユーザーがプロファイルを編集したいとき、ユーザー編集ビューは、以下のUserEditモデルを使用します。

public class UserEditModel 
{ 
    public string Username 
    { 
     get { return Info.Username; } 
     set { Info.Username = value; } 
    } 

    [Required] 
    [MembershipPassword] 
    [DataType(DataType.Password)] 
    public string Password { get; set; } 

    [DataType(DataType.Password)] 
    [DisplayName("Confirm Password")] 
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 
    public string ConfirmPassword { get; set; } 

    [Required] 
    [Email] 
    public string Email { get; set; } 

    public UserInfo Info { get; set; } 
    public Dictionary<string, bool> Roles { get; set; } 
} 

public class UserInfo : IRepoData 
{ 
    [ScaffoldColumn(false)] 
    public Guid _id { get; set; } 

    [ScaffoldColumn(false)] 
    public DateTime Timestamp { get; set; } 

    [Required] 
    [DisplayName("Username")] 
    [ScaffoldColumn(false)] 
    public string Username { get; set; } 

    [Required] 
    [DisplayName("First Name")] 
    public string FirstName { get; set; } 

    [Required] 
    [DisplayName("Last Name")] 
    public string LastName { get; set; } 

    [ScaffoldColumn(false)] 
    public string Theme { get; set; } 

    [ScaffoldColumn(false)] 
    public bool IsADUser { get; set; } 
} 

UserEditModelクラスがIRepoDataから継承するのUserInfoのインスタンスが含まれていることに注意してください? UserInfoはデータベースに保存されるものです。私はIRepoDataフォームを継承し、それを保存するオブジェクトを受け入れるジェネリックリポジトリクラスを持っています。だから私はRepository.Save(myUserInfo)と呼んでいるだけです。 IRepoDataは_id(MongoDB命名規則)とタイムスタンプを定義するので、リポジトリは_idに基づいてアップサンプリングし、タイムスタンプに基づいて競合をチェックし、オブジェクトがMongoDBに保存された他のプロパティをチェックできます。ほとんどの場合、ビューは@Html.EditorForを使用する必要があり、私たちは行かなくてはなりません!基本的には、ビューの必要性だけがベースモデルに入り、リポジトリだけが必要とするものは[ScaffoldColumn(false)]アノテーションを取得し、その他はすべて共通です。 (BTW - 彼らはのUserInfoオブジェクトに含まれていない理由があるので、ユーザー名、パスワード、役割、および電子メールは、.NETプロバイダに保存されます。)

大きな利点このシナリオのが2つある...

  1. は、私は(私の意見では)ので、より簡単に速く開発し、理解し、そしてより保守である少ないコードを、使用することができます。

  2. 私ができる再ファクター秒で...私は2番目のメールアドレスを追加する必要がある場合、私はちょうどのUserInfoオブジェクトに追加するには - それがビューに追加し、追加するだけで、リポジトリに保存されますオブジェクトに1つのプロパティ。 MongoDBを使用しているため、dbスキーマや既存のデータを変更する必要はありません。

この設定では、データを格納するための別のモデルを作成する必要がありますか?このアプローチの欠点は何だと思いますか?明らかな答えは基準と分離の問題ですが、現実世界の例がありますが、これが原因で頭痛の一部が現れると思いますか?

私は2人の開発者からなるチームに取り組んでいることにも留意する価値があるので、いくつかの基準の妥協点を見落とすのは簡単です。あなたは小さなチームで働くことがその点で違いを生むと思いますか?

答えて

9

MVCのビューモデルの利点は、使用しているデータベースシステムにかかわらず存在します(あなたが使用していなくても地獄です)。シンプルなCRUDの状況では、ビジネスモデルエンティティはビューに表示されるものを非常によく模倣しますが、基本的なCRUD以外のものではそうはなりません。

大きなものの1つは、ビューで使用するものと同じクラスをデータモデリング/永続化に使用するビジネスロジック/データの完全性です。ユーザークラスにDateTime DateAddedプロパティがある状況を見て、ユーザーの追加時を示します。それらが追加されたとき

[HttpPost] 
public ActionResult Edit(UserInfo model) { } 

は、ほとんどの場合、ユーザーが変更できるようにしたくない:あなたはのように見えるアクションハンドラで終わるあなたのUserInfoクラスにストレートフックの形を提供する場合あなたの最初の考えは、フォームにフィールドを提供しないことです。

しかし、あなたは2つの理由でこれに頼ることはできません。まず、DateAddedの値は、new DateTime()を行った場合、またはnull(どちらの方法でもこのユーザーには間違っている)の場合と同じ値になります。

これは、ユーザーがフォームリクエストでこれをスプーフィングしてPOSTデータに&DateAdded=<whatever date>を追加することで、アプリケーションはDBのDateAddedフィールドをユーザーが入力したものに変更します。

これは、MVCのモデルバインディングメカニズムがPOST経由で送信されたデータを参照し、モデル内の利用可能なプロパティと自動的に接続しようとするため、これは設計によるものです。これは、送信されたプロパティが元のフォームにないことを知る方法がないため、それをそのプロパティにバインドします。

ViewModelsにはこの問題はありません。ビューモデルはデータエンティティとの間の変換方法を知っていて、表示する必要がある最小限のフィールドしか持たないDateAddedフィールドはありません。または受け取る)ことができます。

正確なシナリオでは、ビューモデルがデータエンティティに直接アクセスできるため、POST文字列操作で簡単に再現できます。

ビューでデータクラスを直接使用する際のもう1つの問題は、データをモデル化する方法に実際に適さない方法でビューを表示しようとするときです。例として、あなたがユーザーのために、次のフィールドを持っているとしましょう:

public DateTime? BannedDate { get; set; } 
public DateTime? ActivationDate { get; set; } // Date the account was activated via email link 

は今、あなたは、すべてのユーザーの状況に興味を持っている、あなたのように次の各ユーザにステータスメッセージを表示したいのは、管理者として言わせて管理者がそのユーザーのステータスに基づいて実行できるさまざまなアクションを提供します。あなたのデータモデルを使用している場合は、あなたのビューのコードは次のようになります。

// In status column of the web page's data grid 

@if (user.BannedDate != null) 
{ 
    <span class="banned">Banned</span> 
} 
else if (user.ActivationDate != null) 
{ 
    <span class="Activated">Activated</span> 
} 

//.... Do some html to finish other columns in the table 
// In the Actions column of the web page's data grid 
@if (user.BannedDate != null) 
{ 
    // .. Add buttons for banned users 
} 
else if (user.ActivationDate != null) 
{ 
    // .. Add buttons for activated users 
} 

これは悪いです、あなたが今、あなたの意見では、ビジネスロジックの多くを持っているので(禁止のユーザステータスは、常にユーザーを禁止し、有効に利用者に優先します禁止された日付のユーザーなどによって定義されます)。また、はるかに複雑です。

代わりに、ステータスの列挙型を持つViewModelでユーザーをラップし、モデルをビューモデルに変換すると(ビューモデルのコンストラクタは最適な場所ですこれを行うには)ビジネスロジックを一度挿入してすべての日付を見て、ユーザーがどのようなステータスであるべきかを把握することができます。

次に、あなたのコードは、上記のように簡略化されています

この単純なシナリオでは、以下のコードのように見えるが、ユーザーのステータスを決定するためのロジックがなったときに、それは物事がたくさんより保守ますしないことがあり
// In status column of the web page's data grid 

@if (user.Status == UserStatuses.Banned) 
{ 
    <span class="banned">Banned</span> 
} 
else if (user.Status == UserStatuses.Activated) 
{ 
    <span class="Activated">Activated</span> 
} 

//.... Do some html to finish other columns in the table 
// In the Actions column of the web page's data grid 
@if (user.Status == UserStatuses.Banned) 
{ 
    // .. Add buttons for banned users 
} 
else if (user.Status == UserStatuses.Activated) 
{ 
    // .. Add buttons for activated users 
} 

より複雑。データモデルを変更することなくユーザーの状態がどのように決定されるかのロジックを変更できます(データの表示方法によってデータモデルを変更する必要はありません)。

+0

私は基本的に同意します。 5番目のパラグラフでは、モデルバインディングが問題の原因であることを指摘する必要があります(つまり、正しい名前のすべてのフィールドがバインドされます)。モデルバインディングは、特定のフィールドを無視するように設定することもできます。それは危険です。マッピングコードは退屈でエラーが発生しやすいため、AutoMapperはViewModelとのマッピングに適しています。 – mnemosyn

+0

ああ、私はそれを追加します。 automapperは、手動で行うのではなく、ビューモデルとのやりとりを行うためのより良いソリューションだと私は同意します。 – KallDrexx

+1

うわー、私のためによく書かれた答えを作る時間をとってくれてありがとう! – jrizzo

7

TL; DR

は、モデルの少なくとも3つの層は時々、時々、それらが安全に組み合わせることができ、アプリケーションにありません。質問の文脈では、パーシスタンスモデルとドメインモデルを組み合わせても構いませんが、ビューモデルは組み合わせません。

完全なポスト

あなたが記述のシナリオは、直接、任意のエンティティモデルを使用して、同じようにうまく適合。 Linq2SqlモデルをViewModel、エンティティ・フレームワーク・モデル、休止状態モデルなどとして使用することができます。主なポイントは、持続モデルをビュー・モデルとして直接使用することです。あなたが言及しているように、懸念の分離は、明示的にこれを避けるよう強制しません。実際、懸念の分離は、モデルレイヤを構築する上で最も重要な要素ではありません。

典型的なWebアプリケーションでは、少なくとも3つの異なるレイヤーのモデルがありますが、これらのレイヤーを単一のオブジェクトに結合することは可能であり、時には正しいです。モデルレイヤーは、ビューモデル、ドメインモデル、およびパーシスタンスモデルのうち、最も高いレベルから低いものまでです。あなたのビューモデルは、あなたのビューに何があるかを正確に記述する必要があります。ドメインモデルでは、システムの完全なモデルを正確に記述する必要があります。永続性モデルでは、ドメインモデルの保存方法を正確に記述する必要があります。

ORMの概念と目的は多岐にわたっていますが、は説明のとおり、は単なる1つです。彼らの大部分は、あなたのパーシスタンスモデルはドメインモデルと同じでなければならず、ORMはデータストアからドメインオブジェクトへのマッピングツールに過ぎないことを約束します。これは、すべてのデータが1か所から来るシンプルなシナリオでは間違いありませんが、最終的には制限があり、ストレージは状況に応じてより実用的なものになります。それが起こると、モデルは明確になる傾向があります。

ドメインモデルをパーシスタンスモデルから分離できるかどうかを判断する際には、ドメインモデルを変更せずにデータストアを簡単にスワップアウトできるかどうかを判断するのが一番簡単です。答えが「はい」の場合は結合でき、そうでない場合は別のモデルにする必要があります。リポジトリインタフェースは、当然のことながら、利用可能なデータストアからドメインモデルを提供するために適しています。 dappermassiveなどの新しい軽量ORMの中には、永続性を実行するために特定のデータモデルを必要としないため、永続性モデルとしてドメインモデルを使用するのが非常に簡単です。 ORMにマッピングを処理させるだけです。

読み取り側では、ビューモデルは、ドメインモデルのサブセットを表すため、ページに情報を表示するために必要ですが、別のモデルレイヤーになります。彼のすべての友人へのリンクを持つユーザーの情報を表示したい場合、そのユーザーの名前にカーソルを合わせると、そのユーザーに関する情報が得られます.MongoDBを使用しても、それを直接処理する永続性モデルはかなり狂っています。もちろん、すべてのアプリケーションがすべてのビューでこのような相互接続されたデータの集合を表示しているわけではなく、時にはドメインモデルが正確に表示したいものです。その場合、同じプロパティーを持つ特定のビュー・モデルに表示したいものを正確に持つオブジェクトから、マッピングの余分な重みを入れる理由はありません。単純なアプリケーションでは、ドメインモデルを拡張するだけであれば、ビューモデルはドメインモデルから継承し、表示したい追加のプロパティを追加します。つまり、MVCアプリケーションが大きくなる前に、レイアウト用のビューモデルを使用し、すべてのページベースのビューモデルをそのレイアウトモデルから継承することを強くお勧めします。

書き込み側では、ビューモデルは、ビューにアクセスするユーザーのタイプに対して編集可能にするプロパティのみを許可する必要があります。管理者以外のユーザーのビューに管理ビューモデルを送信しないでください。 アクセスするユーザーの権限を考慮して、このモデルのマッピングレイヤーを自分で作成した場合、通常のビューモデルから継承する2番目の管理モデルを作成するよりもオーバーヘッドになる可能性があります。それをadminプロパティで補う。最後にあなたのポイントについて

:それは実際には、より理解しやすいとき

  1. 少ないコードでは唯一の利点です。それの可読性と理解力は、それを書く人のスキルの結果です。しっかりした開発者でさえ、解剖して理解するのに長い時間を要した短いコードの有名な例があります。これらの例のほとんどは、わかりやすく書かれた巧妙なコードから来ています。さらに重要なのは、コードが仕様を100%満たしていることです。あなたのコードが短く、理解しやすく、読みやすいものの、仕様に合わないものは価値がありません。それがすべてのもので仕様を満たしていても簡単に悪用できるものであれば、コードは無用です。

  2. 秒単位で安全にリファクタリングすることは、よく書かれたコードの結果であり、それは敏感ではありません。 DRYの原則に従うと、仕様が目標を正しく満たす限り、コードは簡単にリファクタリング可能になります。モデルレイヤーの場合、ドメインモデルは、メンテナンスが容易でリファクタリングが容易なコードを書くための鍵です。ドメインモデルは、ビジネス要件が変化するペースで変化します。ビジネス要件の変更は大きな変更であり、新しい仕様が完全に検討され、設計され、実装され、テストされているかどうかを確認するために注意を払わなければなりません。たとえば、今日、2番目の電子メールアドレスを追加したいとします。あなたはまだビューを変更する必要があります(あなたが何らかの足場を使用していない限り)。また、明日100通までのメールアドレスのサポートを追加するための要件変更がある場合はどうなりますか?最初に提案した変更は、どのシステムでも簡単でしたが、大きな変更にはもっと多くの作業が必要でした。

関連する問題