mvvmuwptemplate10

MVVM, UWP & Template 10 - Independency of model from view technology


My understading of MVVM is that View is responsible for user presentation logic, view-model for interaction logic and specific data transformation from UI independent model classes and model itself representing business domain from data point of view.

Key point is that model classes should be UI indenpendent.

Here comes UWP dev model and Template 10 BindableBase from which model classes are supposed to be derived from. It's quite handy but it ties the model to the specific UI implementation, namely UWP + Template 10.

I've got a data access layer spitting out domain object I want to feed directly as model to the UI. The domain is quite complicated. What I don't wan't to do is to reimplement data domain objects in the UI nor I wan't to pollute it with UI specific functions.

Any thoughts on this?

thank you


Solution

  • With all due respect, I think you might be thinking about this wrong.

    The goal of MVVM is to separate your logic exactly like you describe. The intent of this separation is to simplify your code. Does this make view-models testable and separate concerns? Yes. But, I argue those are secondary goals to simplicity – which keeps your code manageable and maintainable.

    Here’s another way to say it.

    MVVM is a terrific approach for XAML applications. But, if MVVM did not make your view-models testable or separate concerns, MVVM is still a terrific approach for XAML applications because it simplifies code so much – smaller, simpler, and isolated.

    Worth every penny.

    You might argue that MVVM creates another layer of code that developers must understand before they can reason and contribute to the base. I would agree. But I would NOT conclude that MVVM is therefore impracticable. That added layer is trivial compared to the otherwise coupling of logic.

    Now to your question.

    Your data layer object, probably a data transfer object, is remarkably like your UI object, probably a model. Your developer instincts compel you to unite similar things through polymorphism, code generation, or interfaces to avoid added opportunity for bugs, complexity, and tests.

    Consider this.

    Objects created in your data layer are created for your data layer. Likewise, objects created in your UI layer are created for your UI layer. Similarity in structure is a byproduct of similarity in domain. Of course, they are similar: their intents, however, are not. Why would you merge them?

    You already see the problem.

    The only differences you have are, perhaps, a data layer constructor taking in some type of data reader and populating properties. That is perfect for the data layer but inappropriate for the UI layer. Your UI layer might have messaging events or a custom method to handle interaction. That is perfect for the UI layer but inappropriate for the data layer.

    So, where’s the similarity?

    I think developers, including myself, tend to see similar things as potentially identical. Your data-relevant features do not belong in your UI nor the other way. Instead what you need to do is to see the similarity but recognize that they are drastically different objects and don’t deserve to be made the same.

    We’re lazy.

    Duplicate code isn’t about tests and bugs. Not really. It’s about how we, as developers, are so obsessed with “Work smarter not harder” that we tend to look down on “working hard”. If an object is built for one layer it should be in that layer, and not shared by another. I strongly feel this way.

    The right solution

    The easiest solution is to let your DTO serialize on the data layer from DataLayer.Object and then deserialize it on the UI layer as UILayer.Model. This is the easiest and simplest approach and adequately allows you to coerce the data and the object API for the use by its unique layer.

    This means there are two nearly identical objects: one on the data layer and one on the UI layer. But it does NOT mean there are two identical objects. They are not identical because they have functionality unique to each of the tiers, each of the layers.

    What if there is no functionality?

    It makes sense to wonder if this applies to objects and models that have no added functionality. I believe strongly that objects and models in any layer without added functionality are simply waiting for that functionality to be added. Should you assume there is none and build to that assumption, you force the future you or forthcoming maintenance developers to never add layer-specific functionality.

    Does that matter?

    I think it does. Why? Because layer-specific functionality in an object or model allows me to add sophistication (not complexity) to my architecture and implementation within the context of the data object or model, and not in an external construct like a manager, helper, or utility. There is no question that Type.DoSomething is easier than Helper.DoSomethingForType(Type).

    I actually have three

    Just so you know, here’s how I do it: in my projects, you and I probably have similar data layers/services. But, my UI layer actually has TWO models – not one. (Let’s pretend Users is the data type.) My UI layer has json.User and Models.User. The json.User is a bare bones structure, deserialized from my service, and matches the service object exactly. But my UI rarely needs the Service API surface/structure.

    Service(DataLayer.User) > | net | > UI(json.User > Models.User)

    So, then json.User is used to create Model.User, whose structure meets my UI layer’s needs exactly - including methods, events, and messaging constructs. I am free to change my Models.User as I add features to my UI, too - including merging data from other/new data services. Also, Model.User can implement INotifyPropertyChanged where the data layer objects and the json objects never would (or need to).

    Consider this

    If you keep your models separate and you keep one codebase from improperly influencing another, then any change in your database or change in your data service/layer does not REQUIRE a change in your UI layer, even if you change the API surface. Only json.User changes or tweaks to your deserialization hints in your UI layer are impacted. To me, this only makes sense.

    But what about testing and bugs?

    Tests rarely test structures. Data structures are the simplest thing you can add to a solution and rarely contribute to complexity. You can reason over a structure in about a second. You can reason over a method in about a minute. Structures have very little cost. They also have very little construction cost. If you copy/paste the initial structure from your data layer to your UI layer – that’s what we all do. But that is NOT duplicating code. It is just building similar objects appropriately decoupled.

    That’s what I think.