In “About Face”, Alan Cooper showed us how the Save command in most desktop programs forces users to change their conceptual model. The user has a model of how the domain works, and that model does not include things like memory and files. When an application provides a Save command, it forces the user to see the implementation detail that data on the disk can be different from the data in memory. This is an artificial boundary
This artificial boundary becomes increasingly visible when the user exits the program. If they haven’t hit that Save button, then they get a prompt. This prompt points out that the version of their document in memory is different than the one on disk. It “stops the proceedings with idiocy”. The user wanted to quit the program and move on to something else, but the idiot program interrupted them to satisfy its own implementation detail. That detail – the artificial boundary – is unimportant to the user.
Boundaries on the web
On the web, there is no Save command. Users make their changes, and those changes are naturally saved. The artificial boundary doesn’t exist … or does it?
The web actually has a much bigger artificial boundary: that between the browser and the server. The data that the user enters is not saved until they hit submit. Even then, there might be a network problem that prevents them from submitting the form. There might be an authentication step that they have to complete. And there is always the very real risk that the work that the user is trying to save won’t be there after they correct the problem or hit the Back button.
So we rely on AJAX to solve this problem. As the user enters data on the page, we can post back to the server. We don’t have to wait for them to click Submit. We don’t have to show them the artificial boundary, unless something goes wrong. Then, of course, all bets are off and we have to tell them that we failed to save their data.
Boundaries in code
The AJAX solution is a reasonable one. Unfortunately, it creates artificial boundaries within our code. For example, in an MVC application, we have one programming model for postback-style web pages, and another programming model for AJAX.
In a postback-style web page, we inject data into the page on the server using Razor. Then, when the user clicks Submit, we pull data back out of the page using the model binder.
But in an AJAX-style interaction, we can use neither of these. We send data to the client through a REST endpoint as JSON, and then receive it in a similar fashion. All of the data injection, binding, and DOM manipulation occurs in JavaScript rather than Razor.
This is an artificial boundary, too. It’s one that we have imposed upon ourselves. But this isn’t the way it has to be.
Recognizing boundaries
A boundary in software exists when the state on one side can differ from the state on the other. The document in memory can be different from the one on disk. The data in the browser can be different from the data on the server. Sometimes we can eliminate these boundaries. Sometimes these boundaries have to exist. But we must recognize them as accidental complexity – not essential to the problem that we are actually trying to solve.
When you see a view model with fields or auto properties, that is an artificial boundary that we can eliminate. Fields and auto properties can store data. That data can become different from its source. To eliminate this boundary, write get and set methods in your properties that reference the source directly. That’s what I’ve done with Update Controls.
When you see data on disk and in memory, that is another artificial boundary that we can eliminate. Let the in-memory cache be the only way to access the persisted data. Write through the cache so that you know it always represents the persisted data. That’s what I’ve done with Correspondence.
When you see two machines separated by a network, then there is an artificial boundary that we can’t eliminate. But at the very least, we could define a consistent, composable programming model that recognizes the boundary, yet doesn’t force you to code against it. This programming model should be Again, that’s what I’ve done with Correspondence.
In future posts, I will lay out the programming model that remains consistent across artificial boundaries, and eliminates them entirely when possible. I will show examples in client applications first. But eventually, I will apply these principals to web applications. The goal is to reduce defects by making all programming consistent and composable across boundaries.