I'm thinking that they have similarities, but that they're not really the same as MVC as far as I understand it.
So here's what I saw coming up all the time with Phresheez.
- There's a dynamic piece of data that needs to be fetched from the server
- There's a piece of HTML content I generate from that data
- That content needs to be refreshed as new data comes into the server
- I want to use that content in multiple places, but they may want to be formatted for different uses
The rest of this post is what I'm thinking the architecture should be.
Goals
First off, I guess I should state what I want to provide.
- A data abstraction layer that takes care of all of the grotty details of network errors, retransmissions, refreshing, etc, etc. Before refactoring, this code was ~duplicated all over the place each with its own subtle set of bugs. I want just one set of bugs.
- A cleanish separation between content formatting and container decoration. By container decorations, I mean things like menus/nav, titles, last updated, etc.
- I want to be able to facilitate mixing and matching different widgets. That is, I want to be able get content from widget A, widget B and widget C and mix and match them into a new super widget D.
- A recognition that there's some interplay between some of the aspects and not go crazy trying to ferret out every soi-disant layering violation
Data Layer
The Data Layer should also have error callbacks for when something goes wrong. The Data Layer must never affect the DOM in any way: it is up to its subscribers to determine what is displayed.
There may be many Data Layer objects running around with different parameters to the same underlying service. For example, you might have an object that fetches the user information for user1 and another that fetches it for user2. When the Data Layer publishes the data, it must be specific to its instance.
As an optimization, however, it would be good for the Data Layer instances to know about each other such that an instance that has the same url parameters as another will share the server's results with other Data Layer objects so as not to generate useless duplicated calls to the server. I suppose that one way to accomplish that would be for them to subscribe to each other, or maybe a global data cache layer on top of the server callbacks, but that's an implementation detail.
Container Layer
The container layer is the business end of this model. It alone is responsible for inserting/updating elements within the DOM. The normal data flow is that the Container Layer subscribes to some set of data layers (or not!) and upon a published update calls the necessary content binding objects to populate the container (normally a div, but it could be anything's innerHTML really). I'm going to say that it's also its job to deal with layout and general look and feel for the container since a container may contain many objects. So it produces HTML as well, but it's much more oriented at layout than content per se.
The container layer is normally the recipient of the Data Layer's publications. It is responsible for painting the placeholders when the data is not yet available (eg, loading spinners, etc), and coordinating when it's interacting with multiple Data Layers. The Container Layer is also responsible for unsubscribing to the data layer when it has lost focus, for example, to cut down on useless chatter to the server.
Finally, the Container Layer is responsible for implementing any callbacks (eg, onclick) that the container content requires. Which is another way of saying that the Container Layer owns the interaction with the DOM.
Content Binding Layer
The content binding layer binds data from the server to html layout and content. It never interacts with the DOM. It merely takes the data, typically provided by the data layer, but not necessarily exclusively and grinds out the content for that data. The Content Binding layer should be thought of as effectively "stateless" even though for efficiency the Content Binding Layer ought to provide one other service: sameness. That is, if it produces the same content that the caller was returned the last time, it should inform the caller. This is needed to eliminate useless redraws at the container layer which are annoying. Note that if the content layer needs to do extensive massaging of the dynamic data, it is not a layering violation to hold onto the dynamic data (or a massaged form of it) if it wants to do delta's of old and new datasets, etc.
It could be argued that the sameness property ought to be up to the Container Layer, but I don't think so. The Container Layer is the one that knows which bits and piece of dynamic content its using, so it alone is the one that knows if they are the same, modulo comparing blobs of HTML at the Container Layer, which I think is bad practice.
Clicks and other Events
It's not entirely clear to me who should be generating the onclick='s (typically). In the MVC pattern, I'm pretty sure that's the job of the controller, which would be more or less equivalent to my Container Layer. But that would be rather messy in practice: if you're just producing a bunch of html in the Content Binding Layer, it's much easier to insert the onclick's inline with the generated HTML. The downside here is that at the very least the onclicks need to be parameterized with the instance they represent from the Container Layer (ie, this click is associated with this container instance). And of course, the click actions across different container layers may well be very different.
What I think is probably the best compromise is let the Content Binding Layer generate the onclicks, but with input from the Container Layer's state. This still runs afoul if you want something like
<a href=# onclick="st.mywidget.clickHandler('mike')">Click me!</a>
since it is the container layer's job to handle that click, so why should the Content Binding Layer be setting up its callback parameters? Suboptimal, but in practice it haven't noticed many instances where I have one use with a set of parameters and in another use with a different set of parameters... they almost always follow a similar pattern, though you may end up with the union of parameters the various uses require.
My guess is that not stressing about this fuzziness and the apparent layering violations is best. The object here is to get better code reuse and separation of function, but that doesn't mean that we have to solve world hunger as well.
Inserts/Updates
For insert/update type callbacks to the server, I think I would keep it completely as a property of the Container Layer. This is the flip side of the argument about clicks in the previous section: the Content Binding Layer is the one that generates the input's, checkboxes, etc, so why shouldn't it provide the interface to fetch their values? I dunno, because it's sort of fuzzy in the same way that clicks are fuzzy and it would probably be messy to have a grand unified theory here. So some give and take between the Container Layer and Content Binding Layer seems reasonable.
Likewise, I don't see any particular advantage to tasking the Data Layer with the ajax callbacks to the server to save/update state. The point of the Data Layer is to deal with multiple users of data, and keeping it refreshed. It doesn't need to be the singular owner of HttpXMLRequest as well.
Your model is entirely new, and not related to MVC, and that's a GOOD THING. MVC does not belong in the 21st century, period.
ReplyDelete