This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

And another thing about MVVM

Description

I recently wrote <a href="{app}/view_article.php?id=5289">Real quick on MVVM</a> and now I see that a good colleague and friend has written his own <a href="https://ajone239.github.io/2025/02/15/mvvm-understanding.html" author="Austin Jones" source="Austin's Journey for Meaning">MVVM understandings</a>. His piece got me thinking again about how the concept is a good start but isn't really sufficient. <h>Justifying the view model</h> Somewhere near the beginning, he writes, <bq>The View Model’s function is separate from the Model. Abstraction requires discipline to not let two pieces of code that do the same thing <i>become</i> the same thing, purely out of convenience. Things that operate together should be functionally coupled, not just that same code.</bq> While I deeply appreciate the sentiment, I think that (A) most people are going to be unconvinced that they need additional complexity for such a vague goal, and (B) there are more concrete reasons to keep them separate. In <a href="{app}/view_article.php?id=5289">Real quick on MVVM</a>, I posited a simple example, repeated below. <code> record Person( string FirstName, string LastName, Company Company, DateTime BirthDate); </code> The view model might want to expose: <code> int Age => DateTime.Now.Year - _model.BirthDate.Year; string FullName => $"{_model.FirstName} {_model.LastName}"; Company Company { get; } IReadOnlyList<company> AvailableCompanies { get; } </code> The <c>AvailableCompanies</c> is for the drop-down menu. The data in the model is a different <i>shape</i> than that required by the view. This happens <i>quite quickly</i> and <i>quite often</i>. Anyone who tries to "cheat" by using a type as both a model and view model will quickly be writing spaghetti code. It is the view-model's job to marshal data to and from its own shape to that of the model. It is decidedly <i>not</i> the model's job to do that, because it <i>exposes</i> data, while one or more views might display it in different ways. Perhaps another view is showing the birthdate directly, in which case <i>that</i> view model would simply pass the value through unmodified. <h>Thinking through an example</h> <bq>Most logic seems to fall into the View Model as your business logic rules are often mirrored by presentation rules. E.g. a button has to be disabled if the user hasn’t met some requirement.</bq> It may seem too picky but I would instead use the verb <i>reflect</i> instead of <i>mirror</i>, to say that the view model exposes properties that <i>reflect</i> the state in the model. Just off the top of my head, I can imagine that each component of the architecture has unique duties, as illustrated in the example below, <ul>A <b>model</b> contains several properties that must adhere to certain rules in order to be saved. A validation <b>service</b> determines whether those rules have been satisfied, returning a list of zero or more validation results. A <b>view model</b> exposes the most recent list of validations as a property, as well as a property called <c>readyToSubmit</c>. The view model triggers the service to calculate a new list of validations when the view <i>notifies</i> it that a relevant change has been made. A <b>view</b> binds the validations as it sees fit---either attaching properties directly to the controls that will display their values and allow users to manipulate them, as well as exposing the list of validations to the user in some way---as well as binding the <c>Enabled</c> property of the submission button to the <c>readyToSubmit</c> property.</ul> This is just a simple example but we can see that the model is just a data container. In classic OO, the service would have been part of those objects. However, it's far more flexible to keep the model as a set of "dumb" DTOs and to keep the logic in the service. This makes it much easier to replace the validation logic in specific cases, without touching the data layer, which doesn't need to change. The view model does the work of managing calls to the validation service as well as retaining the results as long as the view needs them. The view model doesn't know anything about buttons. It doesn't need to know that they can be enabled or disabled. That's the view's job, which deals with the actual representations presented to the user. This makes the view model, in turn, flexible enough to be used with alternate representations. For example, we can imagine a view or view model that simply auto-saves when <c>readyToSubmit</c> is <c>true</c>, so it would have been a shame to have named that property <c>saveButtonEnabled</c> because it would have been an awkward fit for the hypothetical second view. <h>Benefits</h> As you can well imagine, it's incredibly easy to test systems built in this way, as you can very easily construct the data/model that you want and test something like the validation service. You can also very easily build on top of that to verify that the view model updates and notifies as expected. You can even bind to its properties to verify that a potential view would have received the expected notifications. The view doesn't have more logic in it than <i>binding</i>. Views are more finicky to test---although it's not impossible or even especially difficult with practice, its it's also not usually necessary. For most problems that crop up, your tests eliminate the possibility that the bug is anywhere other than the view, so you quickly find where the incorrect binding. Obviously, if errors like this are chronic---or if you have very complex views---then you'll want to test the view with end-to-end tests. Just remember that testing the view usually requires the most effort, results in the slowest tests, and provides the least benefit, so you should really be doing those last, if at all. <h>Conclusion</h> My colleague's example focuses more on how the service layer pertains to persistence, for loading and storing models. I wanted to provide an example that doesn't have anything to do with persistence but shows that there is non-persistence logic that obviously---at least in hindsight---doesn't belong anywhere but in the service layer. I've been working with this type of abstraction since at least 2002, when I started working on the Atlas framework at Opus Software AG, which was written in Delphi Pascal. We didn't call it MVVM but we had a very clear separation between the <i>object model</i>, the <i>view model</i>, and <i>renderers</i>.