Stateless MVC
Last week, we looked at stateful MVC. Let’s continue with the rest of the story.
What does it mean to be stateless?
Obviously, applications still have state. The thing that really distinguishes “stateless MVC” is that it’s a design technique for interacting via a stateless protocol. Since the canonical stateless protocol these days is HTTP, we’ll just assume it’s that from now on.
Further, these stateless applications, while they still have state, store that state externally to the application. Since the canonical external state is a database, we’ll just assume that’s what we’re working with from now on.
So in contrast to stateful MVC, which stores state internally in the application and which is responding to user interface events, stateless MVC is a design for responding to external messages while externalizing application state. These two design contexts are quite different. That both of these end up being called “MVC” is really a committed effort to stretch the metaphor as far as possible.
Once again, I suspect there’s some foible of human psychology involved here. Maybe we just love analogies.
How are these designs different?
Actually, let’s start with one way these are remarkably pretty similar. Last week, we saw two major variants on stateful MVC. First we had the original, where controllers were about events, and views about drawing. Then we had the “OO UI toolkit” evolution on that original design, where events originated with the views, but were now handled (i.e. given policy) by the controller.
Stateless MVC actually throws back to the original here: controllers in web applications are entirely about events, independently from views. Views have gone back to really being just about “rendering” (in a sense).
But a number of things have changed:
-
A pretty core part of stateful MVC is how the model notifies the views of changes, so they can update themselves. This is just… gone. This alone is a pretty good reason to be suspicious of calling these the same thing.
-
Although models reflect external state, they do not store any state internally. Likewise, the views do not internalize any state either. As a result, much of the inherently object-oriented flavor of design present in stateful MVC is actually quite out the window for stateless MVC.
-
The concerns of each module still have superficially similar roles, but the details of each have changed pretty radically. This can create some confusion about the purpose of each module. This isn’t helped by “fat-controller” vs “fat-model” debates.
A five-module decomposition. “Diamond MVC”
Rather than go over these confusions, I think we should just skip over them and go straight to a tool I think helps clear them all up. The 3-module MVC decomposition leaves us constrained in our thinking, and leaves us trying to jam a “natural” design into a pre-determined box. I think the easiest way to clear everything up is to start with throwing out the limitations.
Let’s make a 5-module variant of “MVC” (because hey, everyone else just re-uses the same name, right?)
Here we get 5 different modules I’ve come up with provisional names for, since I wanted to avoid “controller” and “model:”
-
Router is the the only module that knows anything about HTTP. Every other part of the system has no knowledge of how the request is really being made. The router’s responsibility is to call into the domain logic, and then render that response data with an appropriate view.
-
View is the only module that knows anything about HTML, or JSON, or other “renderings” of the response. I’m happy to call this “view” in common with traditional stateless MVC, because it’s role is largely the same. Except for that dependency on…
-
Schema defines the plain old data types that views operate on. Notably, the schema module has no knowledge of the database, nor any dependencies on any of the rest of the system.
-
Logic is the “business (or domain) logic” of the application. The router will pull the necessary information out of the HTTP request, and call into this module as quickly as possible to do all the actual work.
-
DAL, for lack of a better term (borrowing this one from “data access layer” since I don’t want to use “model”), is the only module that does any talking to the database, or any other IO or interaction with other kinds of externalized state for that matter.
It’s usually better to know what a module can’t do than what it is supposed to do. But second best is to know what a module can do that no other module can, since that’s a description of what a whole bunch of other modules can’t do. This decomposition confines certain things—HTTP, Database, HTML—to specific modules, and ensures that the “business logic” and “application data” are pretty independent of all those concerns.
Relationship to existing stateless MVC models
Which module corresponds to the “model” or the “controller”?
There’s actually a lot of ways to answer these questions; I invite you to come up with your own answers before you look at mine.
The obvious answer is to say there isn’t a correspondence, but that’s not really an answer because we should explain why.
One possible answer is to claim the “router” is the controller. This is likely your answer if you have a preference for keeping logic associated with the model. From this perspective, my decomposition starts to look like I’ve just pulled apart the model into pieces for some reason.
But one could also argue that “logic” is the controller. Many HTTP frameworks attempt to making routing happen as “auto-magically” as possible, with reflection or annotations on the controller. So from this perspective, the router module came for free as part of the framework, and isn’t included in the MVC decomposition. (On the other hand, I clearly show the router being responsible for calling a view to render… but you can always argue I’ve made an error!)
Similarly, we have a couple options for what the call the “model.” On the one hand, obviously it’s the part that talks to the database, right? On the other hand, obviously it’s the module the view reads from in order to render, right? So which should it be, “DAL” or “Schema?”
In my experience, the traditional 3-module decomposition forces us to do two things:
-
For multiple reasons (that we’ll get into), the “DAL” and “Schema” modules are always collapsed together. The view directly interrogates the database-model for the data it needs to render. Although I think this collapsing is slightly unfortunate (it is frequently the case that code in the view will trigger database requests), it is quite consistent with stateful MVC!
-
After collapsing together those two modules, we have something that almost fits the MVC model so closely that to force it to fit, we’ll just pick collapsing “Logic” into either “Router” or “DAL”, depending on our preferences.
Oddly enough, one of the variants of MVC that comes closest to displaying the kind of separation I show between “DAL” and “Schema” is MVVM, which is actually a form of stateful MVC. I’ve never actually used this myself, but from its description, it’s an approach to stateful MVC specifically for use with a database back-end.
MVVM is meant to address adapting a poor model (what’s actually in the database) to a more application-appropriate data model (the “view model”). This adaptation isn’t necessarily a result of bad schema design (though maybe), it can also be a result of the stilted relationship between the relational model and other models of design. (Think: “Every reason why ORMs are sometimes bothersome.” Or just “trees don’t map easily to traditional relational databases.”)
But in any case, now that I’ve gone over the difference, if I were to pick good names for this “Diamond MVC” design, I’d steal the names “model” and “view model.”
Should you use this design?
Probably not.
-
I haven’t attempted it. I don’t want to recommend a design without having tried it out to see if it works well in practice. This is a conceptual design: I found it immensely helpful in understanding what’s going on with MVC-inspired designs in practice, in all their variations. I hope it helps clear things up for you, too.
-
Our programming languages have poor support for “just data.” The “Schema / View Model” module will be more difficult to implement than is really necessary. The whole point of this module is just to define some data types.
-
When writing applications, you’ll likely grate against having to represent data in the database module, and then again represent similar data in the schema module. (Or, for many CRUD apps, perhaps identical data!) There’s a reason people haven’t pursued this approach much out there. Especially when initially writing the application (as opposed to longer-term maintenance), and when the application isn’t too interesting (CRUD), this will feel like pointless duplication.
-
Speaking of which, we’re talking about the design of a whole new framework. You… probably don’t want to design a new framework. Unless you do.