MVC/MVVM/层分离中的 ViewModels - 最佳实践? [英] ViewModels in MVC / MVVM / Separation of layers- best practices?

查看:26
本文介绍了MVC/MVVM/层分离中的 ViewModels - 最佳实践?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对使用 ViewModels 还很陌生,我想知道 ViewModel 是否可以包含域模型的实例作为属性,或者这些域模型的属性应该是 ViewModel 本身的属性?例如,如果我有一个类 Album.cs

公开课相册{public int AlbumId { 获取;放;}公共字符串标题{获取;放;}公共字符串价格{获取;放;}公共虚拟流派流派{得到;放;}公共虚拟艺术家艺术家{得到;放;}}

您通常会让 ViewModel 持有 Album.cs 类的实例,还是让 ViewModel 拥有每个 Album.cs 类的属性?属性.

公共类AlbumViewModel{公开专辑专辑{得到;放;}公共 IEnumerable流派 { 得到;放;}公共 IEnumerable艺术家{得到;放;}公共 int 评级 { 得到;放;}//特定于视图的其他属性}公共类 AlbumViewModel{public int AlbumId { 获取;放;}公共字符串标题{获取;放;}公共字符串价格{获取;放;}公共 IEnumerable流派 { 得到;放;}公共 IEnumerable艺术家{得到;放;}公共 int 评级 { 得到;放;}//特定于视图的其他属性}

解决方案

tl;dr

<块引用>

ViewModel 可以包含域模型的实例吗?

基本上不是因为您实际上是将两层混合并将它们绑在一起.我必须承认,我看到这种情况经常发生,这在一定程度上取决于您项目的快速赢利水平,但我们可以声明它不符合单一职责原则

现在,对于真正肮脏和快速获胜的人来说,它会起作用,但我认为你不应该想要它.

构建一个视图模型只是多一点努力,它通常与域模型有 80+% 的相似度.这可能感觉像是在做不必要的映射,但是当第一个概念差异出现时,您会发现这是值得的:)

因此,作为替代方案,我建议针对一般情况进行以下设置:

  • 创建视图模型
  • 创建域模型
  • 创建数据模型
  • 使用像 automapper 这样的库来创建从一个到另一个的映射(这将有助于将 Foo.FooProp 映射到 OtherFoo.FooProp)

好处是,例如;如果您在其中一个数据库表中创建一个额外的字段,它不会影响您的视图.它可能会影响您的业务层或映射,但它会停止.当然,大多数时候您也想更改视图,但在这种情况下,您不需要.因此,它可以将问题隔离在您代码的一部分中.

Web API/数据层/DTO

首先注意:

注意:名称域模型",有时与 DTO 或模型"混合

请注意,在 Web(或 HTTP 或 REST)API 中;通信通常由数据传输对象 (DTO) 完成,它是实际的事物".暴露在 HTTP 端点上.

那么,您可能会问,我们应该将这些 DTO 放在哪里.它们介于域模型和视图模型之间吗?嗯,是;我们已经看到,将它们视为 viewmodel 会很困难,因为消费者很可能会实现自定义视图.

DTO 是否能够取代domainmodels 或者他们有独立存在的理由吗?一般来说,分离的概念也适用于DTO'sdomainmodels.但话又说回来:你可以问问自己(这就是我倾向于有点务实的地方,);域内是否有足够的逻辑来明确定义 domainlayer?我想你会发现,如果你的服务变得越来越小,作为 domainmodels 一部分的实际 logic 也会减少,并且可能会一起被排除在外你最终会得到:

EF/(ORM) 实体DTO/DomainModel消费者


免责声明/注意

正如@mrjoltcola 所说:还需要记住组件过度设计.如果以上都不适用,并且用户/程序员可以信任,那么您就可以开始了.但请记住,由于 DomainModel/ViewModel 的混合,可维护性和可重用性会降低.

I'm fairly new to the using ViewModels and I wonder, is it acceptable for a ViewModel to contain instances of domain models as properties, or should the properties of those domain models be properties of the ViewModel itself? For example, if I have a class Album.cs

public class Album
{
    public int AlbumId { get; set; }
    public string Title { get; set; }
    public string Price { get; set; }
    public virtual Genre Genre { get; set; }
    public virtual Artist Artist { get; set; }
}

Would you typically have the ViewModel hold an instance of the Album.cs class, or would you have the ViewModel have properties for each of the Album.cs class' properties.

public class AlbumViewModel
{
    public Album Album { get; set; }
    public IEnumerable<SelectListItem> Genres { get; set; }
    public IEnumerable<SelectListItem> Artists { get; set; }
    public int Rating { get; set; }
    // other properties specific to the View
}


public class AlbumViewModel
{
    public int AlbumId { get; set; }
    public string Title { get; set; }
    public string Price { get; set; }
    public IEnumerable<SelectListItem> Genres { get; set; }
    public IEnumerable<SelectListItem> Artists { get; set; }
    public int Rating { get; set; }
    // other properties specific to the View
}

解决方案

tl;dr

Is it acceptable for a ViewModel to contain instances of domain models?

Basically not because you are literally mixing two layers and tying them together. I must admit, I see it happen a lot and it depends a bit on the quick-win-level of your project, but we can state that it's not conform the Single Responsibility Principle of SOLID.


The fun part: this is not limited to view models in MVC, it's actually a matter of separation of the good old data, business and ui layers. I'll illustrate this later, but for now; keep in mind it applies to MVC, but also, it applies to many more design patterns as well.

I'll start with pointing out some general applicable concepts and zoom in into some actual scenario's and examples later.


Let's consider some pros and cons of not mixing the layers.

What it will cost you

There is always a catch, I'll sum them, explain later, and show why they are usually not applicable

  • duplicate code
  • adds extra complexity
  • extra performance hit

What you'll gain

There is always a win, I'll sum it, explain later, and show why this actually makes sense

  • independent control of the layers

The costs


duplicate code

It's not DRY!

You will need an additional class, which is probably exactly the same as the other one.

This is an invalid argument. The different layers have a well defined different purpose. Therefore, the properties which lives in one layer have a different purpose than a property in the other - even if the properties have the same name!

For example:

This is not repeating yourself:

public class FooViewModel
{
    public string Name {get;set;}
}

public class DomainModel
{
    public string Name {get;set;}
}

On the other hand, defining a mapping twice, is repeating yourself:

public void Method1(FooViewModel input)
{
    //duplicate code: same mapping twice, see Method2
    var domainModel = new DomainModel { Name = input.Name };
    //logic
}

public void Method2(FooViewModel input)
{
    //duplicate code: same mapping twice, see Method1
    var domainModel = new DomainModel { Name = input.Name };
    //logic
}

It's more work!

Really, is it? If you start coding, more than 99% of the models will overlap. Grabbing a cup of coffee will take more time ;-)

"It needs more maintenance"

Yes it does, that's why you need to unit test your mapping (and remember, don't repeat the mapping).

adds extra complexity

No, it does not. It adds an extra layer, which make it more complicated. It does not add complexity.

A smart friend of mine, once stated it like this:

"A flying plane is a very complicated thing. A falling plane is very complex."

He is not the only one using such a definition, the difference is in predictability which has an actual relation with entropy, a measurement for chaos.

In general: patterns do not add complexity. They exist to help you reduce complexity. They are solutions to well known problems. Obviously, a poorly implemented pattern doesn't help therefore you need to understand the problem before applying the pattern. Ignoring the problem doesn't help either; it just adds technical debt which has to be repaid sometime.

Adding a layer gives you well defined behavior, which due to the obvious extra mapping, will be a (bit) more complicated. Mixing layers for various purposes will lead to unpredictable side-effects when a change is applied. Renaming your database column will result in a mismatch in key/value-lookup in your UI which makes you do a non existing API call. Now, think of this and how this will relate to your debugging efforts and maintenance costs.

extra performance hit

Yes, extra mapping will lead to extra CPU power to be consumed. This, however (unless you have a raspberry pi connected to a remote database) is negligible compared to fetching the data from the database. Bottom line: if this is an issue: use caching.

The win


independent control of the layers

What does this mean?

Any combination of this (and more):

  • creating a predictable system
  • altering your business logic without affecting your UI
  • altering your database, without affecting your business logic
  • altering your ui, without affecting your database
  • able to change your actual data store
  • total independent functionality, isolated well testable behavior and easy to maintain
  • cope with change and empower business

In essence: you are able to make a change, by altering a well defined piece of code without worrying about nasty side effects.

beware: business counter measures!

"this is to reflect change, it's not going to change!"

Change will come: spending trillions of US dollar annually cannot simply pass by.

Well that's nice. But face it, as a developer; the day you don't make any mistakes is the day you stop working. Same applies to business requirements.

fun fact; software entropy

"my (micro) service or tool is small enough to cope with it!"

This might be the toughest one since there is actually a good point here. If you develop something for one time use, it probably is not able to cope with the change at all and you have to rebuild it anyway, provided you are actually going to reuse it. Nevertheless, for all other things: "change will come", so why make the change more complicated? And, please note, probably, leaving out layers in your minimalistic tool or service will usually puts a data layer closer to the (User)Interface. If you are dealing with an API, your implementation will require a version update which needs to be distributed among all your clients. Can you do that during a single coffee break?

"lets do it quick-and-simple, just for the time being...."

Is your job "for the time being"? Just kidding ;-) but; when are you going to fix it? Probably when your technical debt forces you to. At that time it cost you more than this short coffee break.

"What about 'closed for modification and open for extension'? That's also a SOLID principle!"

Yes, it is! But this doesn't mean you shouldn't fix typo's. Or that every applied business rule can be expressed as an sum of extensions or that you are not allowed to fix things that are broken. Or as Wikipedia states it:

A module will be said to be closed if it is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding)

which actually promotes separation of layers.


Now, some typical scenarios:

ASP.NET MVC


Since, this is what you are using in your actual question:

Let me give an example. Imagine the following view model and domain model:

note: this is also applicable to other layer types, to name a few: DTO, DAO, Entity, ViewModel, Domain, etc.

public class FooViewModel
{
    public string Name {get; set;} 

    //hey, a domain model class!
    public DomainClass Genre {get;set;} 
}

public class DomainClass
{
    public int Id {get; set;}      
    public string Name {get;set;} 
}

So, somewhere in your controller you populate the FooViewModel and pass it on to your view.

Now, consider the following scenarios:

1) The domain model changes.

In this case you'll probably need to adjust the view as well, this is bad practice in context of separation of concerns.

If you have separated the ViewModel from the DomainModel, a minor adjustment in the mappings (ViewModel => DomainModel (and back)) would be sufficient.

2) The DomainClass has nested properties and your view just displays the "GenreName"

I have seen this go wrong in real live scenarios.

In this case a common problem is that the use of @Html.EditorFor will lead to inputs for the nested object. This might include Ids and other sensitive information. This means leaking implementation details! Your actual page is tied to your domain model (which is probably tied to your database somewhere). Following this course, you'll find yourself creating hidden inputs. If you combine this with a server side model binding or automapper it's getting harder to block the manipulation of hidden Id's with tools like firebug, or forgetting to set an attribute on your property, will make it available in your view.

Although it's possible, maybe easy, to block some of those fields, but the more nested Domain/Data objects you have, the more trickier it will become to get this part right. And; what if you are "using" this domainmodel in multiple views? Will they behave the same? Also, bear in mind, that you might want to change your DomainModel for a reason that's not necessarily targeting the view. So with every change in your DomainModel you should be aware that it might affect the view(s) and the security aspects of the controller.

3) In ASP.NET MVC it is common to use validation attributes.

Do you really want your domain to contain metadata about your views? Or apply view-logic to your data-layer? Is your view-validation always the same as the domain-validation? Does it has the same fields (or are some of them a concatenation)? Does it have the same validation logic? Are you are using your domain-models cross application? etc.

I think it's clear this is not the route to take.

4) More

I can give you more scenario's but it's just a matter of taste to what's more appealing. I'll just hope at this point you'll get the point :) Nevertheless, I promised an illustration:

Now, for really dirty and quick-wins it will work, but I don't think you should want it.

It's just a little more effort to build a view-model, which usually is for 80+% similar to the domain model. This might feel like doing unnecessary mappings, but when the first conceptual difference arises, you'll find that it was worth the effort :)

So as an alternative, I propose the following setup for a general case:

  • create a viewmodel
  • create a domainmodel
  • create a datamodel
  • use a library like automapper to create mapping from one to the other (this will help to map Foo.FooProp to OtherFoo.FooProp)

The benefits are, e.g.; if you create an extra field in one of your database tables, it won't affect your view. It might hit your business layer or mappings, but there it will stop. Of course, most of the time you want to change your view as well, but in this case you don't need to. It therefore keeps the problem isolated in one part of your code.

Web API / data-layer / DTO

First a note: here's a nice article on how DTO (which is not a viewmodel), can be omitted in some scenario's - on which my pragmatic side fully agrees ;-)

Another concrete example of how this will work in a Web-API / ORM (EF) scenario:

Here it's more intuitive, especially when the consumer is a third party, it's unlikely your domain model matches the implementation of your consumer, therefore a viewmodel is more likely to be fully self-contained.

note: The name "domain model", is sometimes mixed with DTO or "Model"

Please note that in Web (or HTTP or REST) API; communications is often done by a data-transfer-object (DTO), which is the actual "thing" that's being exposed on the HTTP-endpoints.

So, where should we put these DTO's you might ask. Are they between domain model and view models? Well, yes; we have already seen that treating them as viewmodel would be hard since the consumer is likely to implement a customized view.

Would the DTO's be able to replace the domainmodels or do they have a reason to exists on their own? In general, the concept of separation would be applicable to the DTO's and domainmodels as well. But then again: you can ask yourself (,and this is where I tend to be a bit pragmatic,); is there enough logic within the domain to explicitly define a domainlayer? I think you'll find that if your service get smaller and smaller, the actual logic, which is part of the domainmodels, decreases as well and may be left out all together and you'll end up with:

EF/(ORM) EntitiesDTO/DomainModelConsumers


disclaimer / note

As @mrjoltcola stated: there is also component over-engineering to keep in mind. If none of the above applies, and the users/programmers can be trusted, you are good to go. But keep in mind that maintainability and re-usability will decrease due to the DomainModel/ViewModel mixing.

这篇关于MVC/MVVM/层分离中的 ViewModels - 最佳实践?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆