How to handle view model with multiple aggregate roots?
Asked Answered
P

3

5

At the moment, i got quite badly fashioned view model.

Classes looks like this=>

 public class AccountActionsForm
    {
        public Reader Reader { get; set; }
        //something...
    }

Problem is that Reader type comes from domain model (violation of SRP).

Basically, i'm looking for design tips (i.e. is it a good idea to split view model to inputs/outputs?) how to make my view model friction-less and developer friendly (i.e. - mapping should work automatically using controller base class)?

I'm aware of AutoMapper framework and i'm likely going to use it.

So, once more - what are common gotchas when trying to create proper view model? How to structure it? How mapping is done when there's a multiple domain object input necessary?


I'm confused about cases when view needs data from more than 1 aggregate root. I'm creating app which has entities like Library, Reader, BibliographicRecord etc.

In my case - at domain level, it makes no sense to group all those 3 types into LibraryReaderThatHasOrderedSomeBooks or whatnot, but view that should display list about ordered books for specific reader in specific library needs them all.

So - it seems fine to create view OrderedBooksList with OrderedBooksListModel view model underneath that holds LibraryOutput, ReaderOutput and BibliographicRecordOutput view models. Or even better - OrderedBooksListModel view model, that leverages flattening technique and has props like ReaderFirstName, LibraryName etc.

But that leads to mapping problems because there are more than one input.
It's not 1:1 relation anymore where i kick in one aggregate root only.
Does that mean my domain model is kind a wrong?

And what about view model fields that live purely on UI layer (i.e. enum that indicates checked tab)?

Is this what everyone does in such a cases?

 FooBarViewData fbvd = new FooBarViewData();
   fbvd.Foo = new Foo(){ A = "aaa"};
   fbvd.Bar = new Bar(){ B = "bbb"};
   return View(fbvd);

I'm not willing to do this=>

var fbvd = new FooBarViewData();
   fbvd.FooOutput =  _mapper.Map<Foo,FooOutput>(new Foo(){ A = "aaa"});
   fbvd.BarOutput = _mapper.Map<Bar,BarOutput>(new Bar(){ B = "bbb"});
   return View(fbvd);

Seems like a lot of writing. :)


Reading this at the moment. And this.


Ok. I thought about this issue a lot and yeah - adding another abstraction layer seems like a solution =>

alt text

So - in my mind this already works, now it's time for some toying.

ty Jimmy

Preraphaelite answered 7/1, 2010 at 13:16 Comment(0)
T
4

It's tough to define all these, but here goes. We like to separate out what we call what the View sees from what the Controller builds. The View sees a flattened, brain-dead DTO-like object. We call this a View Model.

On the Controller side, we build up a rich graph of what's needed to build the View Model. This could be just a single aggregate root, or it could be a composition of several aggregate roots. All of these together combine into what we call the Presentation Model. Sometimes the Presentation Model is just our Persistence (Domain) Model, but sometimes it's a new object altogether. However, what we've found in practice is that if we need to build a composite Presentation Model, it tends to become a magnet for related behavior.

In your example, I'd create a ViewFooBarModel, and a ViewFooBarViewModel (or ViewFooBarModelDto). I can then talk about ViewFooBarModel in my controller, and then rely on mapping to flatten out what I need from this intermediate model with AutoMapper.

Trafficator answered 11/1, 2010 at 13:42 Comment(1)
Hoped to see you answering this cause thought you should know (lostechies.com/blogs/jimmy_bogard/archive/2010/01/04/…). Answer for this question is just another brick for successful development of nice & reliable UI tests (and preparing for Mvc2 templating buzz) what's my actual aim. I'll try to summarize whole idea a bit later when i'll get home and then - post another "ping" comment under your answer to get some response. :)Preraphaelite
A
4

Here's one item that dawned on us after we had been struggling with alternatives for a long time: rendering data is different from receiving data.

We use ViewModels to render data, but it quickly turned out that when it came to receiving data through forms posting and similar, we couldn't really make our ViewModels fit the concept of ModelBinding. The main reason is that the round-trip to the browser often involves loss of data.

As an example, even though we use ViewModels, they are based on data from real Domain Objects, but they may not expose all data from a Domain Object. This means that we may not be able to immediately reconstruct an underlying Domain Object from the data posted by the browser.

Instead, we need to use mappers and repositories to retrieve full Domain Objects from the posted data.

Before we realized this, we struggled much with trying to implement custom ModelBinders that could reconstruct a full Domain Object or ViewModel from the posted data, but now we have separate PostModels that model how we receive data.

We use abstract mappers and services to map a PostModel to a Domain Object - and then perhaps back to a ViewModel, if necessary.

Ashton answered 7/1, 2010 at 13:36 Comment(7)
This is a nice tip but unluckily (or luckily) i've figured out this part. Model binding by Id/mapping works like a charm. Another thing - it's good to separate view model into view model you render and view model you receive when form is posted.Preraphaelite
I'm interested how to handle cases when view needs more than one entity view model and they need to be grouped. Any tips about that?Preraphaelite
I'm not sure I understand that additional question, but you can compose ViewModels from other ViewModels. However, you're a smart guy, so you probably know that already, so I guess you meant something else...Ashton
Thanks for the compliment. I'll try to add some explanation in my question. :)Preraphaelite
@Mark Seemann i remember Jimmy wrote somewhere that they do NOT bind view model back to domain model. They perform changes 'manually'. And when you think about it - it sounds correct if we follow DDD. Binding back is one of the things that drags towards anemic domain model and fat service layer.Preraphaelite
Inspired post there, Mark. Your first sentance should be the one of the first things any book, blog post or other such guide says when covering ASP.NET MVC view models in relation to domain objects. It's one of those things which is obvious in hindsight.Urbane
if anyone stumbles upon this - be warned that model binding by id ain't good idea. sacrifices great deal of information what exactly is supposed to be retrieved. breaks down fast with stuff like object relational mapping and eager loading.Preraphaelite
T
4

It's tough to define all these, but here goes. We like to separate out what we call what the View sees from what the Controller builds. The View sees a flattened, brain-dead DTO-like object. We call this a View Model.

On the Controller side, we build up a rich graph of what's needed to build the View Model. This could be just a single aggregate root, or it could be a composition of several aggregate roots. All of these together combine into what we call the Presentation Model. Sometimes the Presentation Model is just our Persistence (Domain) Model, but sometimes it's a new object altogether. However, what we've found in practice is that if we need to build a composite Presentation Model, it tends to become a magnet for related behavior.

In your example, I'd create a ViewFooBarModel, and a ViewFooBarViewModel (or ViewFooBarModelDto). I can then talk about ViewFooBarModel in my controller, and then rely on mapping to flatten out what I need from this intermediate model with AutoMapper.

Trafficator answered 11/1, 2010 at 13:42 Comment(1)
Hoped to see you answering this cause thought you should know (lostechies.com/blogs/jimmy_bogard/archive/2010/01/04/…). Answer for this question is just another brick for successful development of nice & reliable UI tests (and preparing for Mvc2 templating buzz) what's my actual aim. I'll try to summarize whole idea a bit later when i'll get home and then - post another "ping" comment under your answer to get some response. :)Preraphaelite
A
3

While it may not make sense to group unrelated Entities (or rather their Repositories) into a Domain Object or Service, it may make a lot of sense to group them in the Presentation layer.

Just as we build custom ViewModels that represents Domain data in a way particularly suited to a specific application, we also use custom Presentation layer services that combine things as needed. These services are a lot more ad-hoc because they only exist to support a given view.

Often, we will hide this service behind an interface so that the concrete implementation is free to use whichever unrelated injected Domain objects it needs to compose the desired result.

Ashton answered 7/1, 2010 at 14:41 Comment(4)
This makes sense to me too. I'm just wondering about actual implementation from technical point of view. I'm not willing to create countless constructors just to aggregate entities and produce a bit more complex view model. Can automapper give some aid at this?Preraphaelite
I haven't been able to reduce our stuff to something that isn't complex in some way. It's much simpler with rich clients because you don't have all the round-tripping and loss of data. I don't see AutoMapper helping a lot with this, because its force is in convention-base mapping. Maybe you should ask this specific question as a new SO question, and then I promise not to answer :)Ashton
One idea came to mind => action could be decorated with MapTo(typeof(OrderedBooksListModel)) and call view with anon type View("OrderedBooksList", new{library,reader,bibliographicrecord[]}) then - through filter, using some reflection magic and automapper map it properly. Would that be an acceptable solution? Any drawbacks?Preraphaelite
That could possibly work. Drawbacks? Yes: anonymous types used as output are essentially just magig string dictionaries, so you might as well just return a Dictionary<string, object>. The loss of type-safety can potentially bite you as you refactory, but whether that's an acceptible price to pay is a judgment call.Ashton

© 2022 - 2024 — McMap. All rights reserved.