On CQRS, DDD(D), ES, …
version euh … failure. It was monolithic, taking on way too many responsibilities in places you didn’t expect, it had hardly any extensibility points (let alone figuring out where to find them), it was overloaded with technical “look at my neat trick to make it bark” concerns, and – to top it off – it was borderline unreadable. It’s safe to say we weren’t very happy with the result (which would haunt us in the months and years to come). Although, in all fairness, there were some nice gems in there.
So, what were we todo? What any agile team should do … be honest about it and tackle it head on. I started hacking on what I thought “it should be more like” and so did another team member. We sat down as a team, did a code review of the current approach to better understand what the actual problems were, compared it with two more approaches, sollicited feedback, bounced off ideas on how to improve the situation, and settled on a path forward.
It’s this path forward I want to show you a glimpse of. This is a more in depth exploration of what I touched upon in viewmodels like you meant it, but in a slightly different context.
If you want to prevent logic from creeping into your view, if you want to clearly define how visual state relates to a piece of data or to a trigger, you should refrain from using primitive types in your viewmodel, or you’ll end up with something similar to the sample below.
Preventing conditions from creeping into views is easy to solve. You just move the condition into the viewmodel, giving it a descriptive name along the way, maybe using a computed observable if it happens to be dynamic. Easy enough and nothing wrong with doing so (in fact I encourage you to do so). What may be less obvious is how to get rid of the primitives. The answer is simple: building the right abstractions.
Being valid or invalid, having focus, visibility, being enabled or disabled, having a value, change tracking and – in the case of data-binding – being able to pause and dispose subscriptions are all concerns that revolve around that one value most people seem to put as a primitive in their viewmodel. Don’t get me wrong, I’m not suggesting that each and every primitive value requires all these concerns, but I’d rather put them in my abstraction than polluting my viewmodel when I do need them.
Next to TextInput and NumericInput you can imagine DateInput, TimeInput, SelectOneInput, SelectManyInput, but also more domain specific inputs like PercentageInput (constrained numeric input), MinDefaultMaxDurationsInput (composite input), or even HtmlColorInput (formatted text input).
What applies to inputs also applies to triggers, which are most commonly visualized as buttons and links. A trigger could be used to replace the CanSave observable and Save function in the first example.
It’s nice to build and have these abstractions, it’s even better to actually use them. Do realize that, like any abstraction, they emerged from actual viewmodel implementations. For those that think “this is a controls library”, I can only feel pitty, because this has little to do with view specific controls.
This was one of the most fundamental insights we had during this journey: the ability to compose viewmodels using the above building blocks and other viewmodels (that in turn have been composed using those building blocks) made them easier to define and reason about. It also helps to keep them at the same level of abstraction.
This line of thought can also be applied at other levels. It has a very recursive and composite feel to it.
Something that’s been missing from the previous snippets is any form of actual behavior. How is one going to introduce logic/flow and interact with dialogs and server side resources? I tend to associate that coordination task with a controller. “Viewmodels as controllers” starts to crumble really fast when you’ve applied the composition above. You’ll end up with big fat viewmodels that feel like a blatant SRP violation.
Therefor making both the controller and how it’s bound to viewmodels explicit, pays off in readability, testability, and keeping the viewmodels thin. Don’t get too hung up on the details below, there are many ways to implement these concerns.
Below is a glimpse of what the final binding could look like.
This is still very much a WIP, but with what little abstractions we’ve come up with, we’ve been able to untangle most of the mess we were in before. Dependencies are now easy to replace (adhering to SRP pays off). Composition has simplified the amount of detail we have to worry about at each level of abstraction. Making the controller explicit has kept our viewmodels free of unnecessary dependencies. All in all we’ve been much more productive using this approach.