Monday, April 26, 2010

View-Model design question

Here's a design question for you!

Lets say you are working in ASP.NET MVC 2 (or your favorite MVC web framework).  Lets also say you have a nice rich model.  Your controllers have to fetch the model objects and get that data to your view.  How do you do that?

There are a bunch of ways:
  1. Pass the model directly to the view
  2. Create a "View-Model" class and put a property on it that exposes the model
  3. Create a "View-Model" that completely hides the model behind properties of its own
And there are a bunch of variations on those too.  But those are the main options.

The best thing about #1 is it's as simple as can be.
#2 is almost as simple as #1 but adds the ability for you to create other custom properties on the "View-Model" object that can perform various operations for the view.  For example, you might format values, or retrieve the latest object from a list, etc.

The downside to these two options is your View is directly coupled to your Model.  This might become a problem if you end up with lots of Views that depend on the same Model, or if the Model keeps evolving and being refactored over time.

That's where #3 comes in.  By creating all new properties on the View-Model, you're basically applying the Dependency Inversion Principle and saying, "This view needs this data, I don't care where it comes from as long as someone provides it."  You will now need some form of mapping layer to get the data from the Model to the properties of the View-Model.  This is more work, but it's also nice.  When the Model changes, you only need to update the mapping, which is dramatically easier than digging into lots of HTML and finding what could be many references to your properties.

Now, that said, there are still lots of changes that will require you to make changes to the Model, View-Model, and View.  Any change that is a change in the *meaning* of the Model will cascade this way.  But there is a whole set of changes that won't cause this update cascade.  Like any refactoring of the Model for example.

The obvious downside with #3 is more code and more work (Though tools like AutoMapper certainly help).

So, how do you know when to apply which pattern?  Is one pattern always better than the others, or does it depend.  And if it depends, on what?  And how do you know when it's time to switch from one to the other?

Thoughts?  Experiences?

12 comments:

  1. I most often do #1 and have on ocassion later wished we had #3. #3 could save you some work when making large model changes over #1 but this still probably needs to take place in your view-model adapters.

    The place where #3 becomes the best choice in my mind is when your tooling doesn't support refactoring into your view layer (if your IDE doesn't know that your HTML templating language is referencing object properties this is a problem) or when you have a complex domain model with deep integration with the database. I have worked on public facing websites where our model's intimate integration with the database mapping (NHibernate/ActiveRecord) meant that many view logic pieces were inadvertently requesting the same pieces of data over and over again while generating a single page. Using the same model object types everywhere made it difficult to know when going to the database was needed and when it wasn't. A good view-model object could have made this simpler.

    In any case, please, by god, use interfaces and do not make your models final.

    ReplyDelete
  2. Also .... while on the subject of delegating functionality to model objects ... C# (and all 'similar' languages for that matter) need a dirt simple syntactical way of having an object delegate all functions to another W/O needing to generate/create/maintain all of the plumbing code.

    Is it too much to ask for a construct like:

    class ViewModel : IModel([Delegate(myModel)])
    {
    private OurModel myModel; // implementes IModel

    ...

    }

    So that the view Model class automagically delegates all IModel interactions down to the base object and allow it to override anything by explicit defined functions.

    This doesn't solve the situation where you only want to expose some methods (although you could replace IModel with a more specific interface) but it seems like this would greatly reduce boilerplate code and hopefully push some developers to favor composition over inheritence.

    my 2 cents

    ReplyDelete
  3. Curious, why do you want your Model to implement an interface?

    ReplyDelete
  4. In the above instance ... so that my view-model object could expose all of the same behavior without requiring it to be part of an inheritance hierarchy with the actual model class.

    In general, so that the 'real' model object can be interchanged with others (e.g. test stub/mock objects, AOP modified objects with added logging/security/caching, etc)

    ReplyDelete
  5. in general, i would say #3 is the place to start. you will get better performance out of your site by splitting up the Read / Write needs of your system (CQS or CQRS patterns).

    The general rule for when I would do #1 is when there is a distinct need to get the feature out the door NOW!!!! at the sacrifice of application performance and scalability / flexibility.

    #2 would be the scenario where you have to get it done and out the door now, but you realize you need to denormalize your object graph in order to make it work right now.

    ReplyDelete
  6. I would probably disagree and say #1 is the place to start. Why start with the most complicated approach if you aren't sure you need it yet? Keeping a watchful eye on the code and identifying when is the right time to refactor out to #2 or #3 is easier said than done, but in general I like to start simple. If it turns out your application is much less complicated than you thought it was you will thank yourself for it.

    ReplyDelete
  7. @Derick, why would #3 get better performance?

    ReplyDelete
  8. @Ben, I can see the argument for creating an interface for the model so you could mock it for testing. Then you could do TRUE unit testing on your controller (for example) and test only the controller logic and not the controller AND the model.

    But I'm not sure I see why you would want a view-model to implement the same interface as a model. I feel like making the view-model implement the model's interface is breaking some of the abstraction that made you want the view-model in the first place, no?

    ReplyDelete
  9. I'm going with Lee on this one. On the couple of MVC apps i have i have always approached them starting with using the model as my view model. A lot of the time that works.

    When it doesnt i break it into option number 2 and expose, usually, a collection of various data models within a view model.

    It's rare that I use number 3 as that seems to be overkill to me.

    ReplyDelete
  10. re: performance with #3

    CQRS allows you to separate how you load data for view models vs. how you load data for your real object model. you can optimize the view-model to only load the data that you need to display, and you can do it via the fastest known data loading techniques, such as DataReaders in .NET... this can be done with a single call to a stored procedure, a view, or anything else that you want to map data from.

    by contrast, if you are loading up an object graph that is 6 or 8 objects deep, you have to make 6 or 8 calls to the database and then reconstitute all of those objects and their data so that you can then denormalize the data into your view for display purposes. this is a huge cost for something that a relational database is really good at doing.

    for a bit more of a not-quite-case-study on this, see my CQRS Performance Engineering post: http://www.lostechies.com/blogs/derickbailey/archive/2010/03/08/cqrs-performance-engineering-read-vs-read-write-models.aspx

    ReplyDelete
  11. @Derick, makes sense! Skip the model on load so you can optimize for performance.

    Though I'm assuming you'd only do this if you had a real performance concern.

    ReplyDelete
  12. short answer:

    i would start with #3 because I do it enough to mitigate the cost. whether you start with #3 or migrate to it depends on your team, skill set and infrastructure/architecture.

    long answer:

    there's a cost associated with everything we do and every decision we make, based partially on our existing architectures, skill sets, etc.

    if a given individual or team does not yet have an architecture or infrastructure set up for read-only view model loading, then the cost of moving to the CQRS option can be very high.

    even if the team has the infrastructure needed, an individual might not be familiar with getting it done for various reasons - don't know the API, don't know SQL, don't know ... whatever they don't know will increase the cost of the option.

    that being said, if you are not experiencing any performance problems with your current system, and you don't have the infrastructure or skill set in place to get a view-model loaded independently of your full object model, then yes, you should stick with option 1 or 2 for the time being.

    having gone through all three of these options numerous times in many different applications, I don't find any significant cost with starting out in option 3, so i advocate for starting with this and preventing performance problems instead of reacting to them. i find the cost of reacting to the performance problems of view-model data loading is usually more than the cost of starting with good CQRS. again, that's based on me and my team having the infrastructure and skill set to do this, though.

    my suggestion would be to setup a spike or two and practice this option alongside your standard data access type (be that an ORM or DataTables or whatever you happen to use). get used to the idea and the implementation so that when you do find yourself needing the performance improvement, you won't have to incur a significant cost at the last moment, causing corners to be cut in order to get it done as quickly as possible.

    ... of course, my bias at the moment is considering the Compact Framework and the limited resource handheld devices I'm deploying to. however, i've seen the same types of performance problems in web and windows apps in the past, and solved the data access performance issue using the same principle in those circumstances.

    ReplyDelete

Note: Only a member of this blog may post a comment.