Brian's Waste of Time

Mon, 28 Jul 2003

Oh the Overloaded MVC
Everyone talks about MVC these days, and everyone means something different. It has become to loaded a term to be useful in software design anymore, methinks. In order to make it useful you have to specify "Smalltalk mvc" or "NextStep mvc" of "web mvc" (whatever that is) or the marketing "[X]mvc[Y]" of the day (ie, Pull-MVC, MVC2, HMVC, TIRMVCBMVCIM ( ThisIsntReallyMVCButMVCIsMarketable )).

The important part of MVC is the seperation of concerns. It makes the data model, the application model, the user interface, the control interface, etc relatively orthogonal. There seems to be a concensus in a lot of "MVC" chitter-chatter to use the NextStep MVC as a model for the "Web MVC". This is lead by the Jakarta Struts project - a really powerful and useful tool for building web applications. It has made some dead-wrong decisions in my opinion, but those opinions are directly related to following the NextStep MVC pattern.

The NextStep MVC pattern basically makes the View and Model layers independent of each other - neither knows anything about the other. The Controller maps everything both ways. In the NextStep Objective-C UI builder tool, this was pretty easily done by dragging and dropping widgets to fields of methods on the model. Your controller was autogenerated for you, almost. The idea behind using this for Web MVC is that you have a fat controller (ie, Struts) that knows everything about the user interface, and everything about the model, and it draws the dots between the two (or rather, executes Actions to modify the data model, and loads forms to display stuff to the user).

The idea that this allows decoupling of application model from the user interface. It sort of does. The problem is that the user interface is the web, period, if you use Struts. If you then put domain manipulation logic in Struts Actions you are tied to Struts. Whole piles of libraries have been developed to break this tie. The "official" (if there is one) technique seems to be the use of ProcessBeans from the Scafffold library which are populated via reflection and executed by the Action. You can map the Action to the Process bean in the URI, metadata, etc. Here though, we find we are pushing the real Controller logic into a seperate layer - the ProcessBean layer which then interacts with the application model, ideally using the ProcessBean as a Command object to effect various changes on the model. Cool. We managed to push the Control code out of the View (which Struts has become a part of now since it isn't allowed to know anythign about the processbeans except invia metadata.

We have a new problem though, and this is the one that is tough to beat. The control over the "next view" bit, so critical in web stuff as you cannot just leave it static when you execute a request, is controlled by the same Action in Struts. It is supposed to tell the servlet where to go next. You can use keywords in metadata and define a few thousand mappings for all the possibilities, but each one must be enumerated somewhat verbosely in XML metadata. Your view is controlled by your Controller, which is fine in NextStep but bloody awkward in Web - it slams reusability of actions into the ground and you wind up having to enumerate every possible option in metadata.

Furthermore, any display of information from the application model needs to be populated into the Session, Request, or whatever contect you want to day by the Action - either that or load it directly in the JSP. We now have the view and model seperated (some) for application modification, but as soon as you render something it is invalidated. The NextStep model presumed application boundaries were not breached, so it could use events from the model to trigger updates in the View. The view didn't know about the model, but was updated by it (via redirection of updates in the Control. The Controller talked both ways. There is no immediate parallel for the Controller to do this on the web. The only time the user interface is updateable is when a requiest is issued, and that is typically to make a change (or use a different view). There is no mechanism for the application model (on a seperate process, machine, continent) to force updates to the real user interface.

The Smalltalk MVC concept chanegs this - it makes the view aware of the model. In Smalltalk world the Model fired events to the View telling something changed (and religious wars raged over how fine or course grained this info should be) but the general concept was that the View knew the model had changed and hence re-queried the model for its new state. The Controller was truly Bridge that directly mapped UI events to model method calls. The controller knew nothing about what was calling its methods.

This falls apart on web based applications, and worse big fat J2EE enterprise component apps, because the view depends on the controller, which depends n the model. The View also knows the model. Lots of knowledge going on here, doesn't lend itself to Visio, doesn;t lend itself to swapping out layers.

With a few small changes it dows though. The application model provides two interfaces to the world in this model - the Control interface and the View interface. The Control interface encapsulates Command objects that know how to modify the application model, the View interface provides a read-only view of the Model. The Control is necesarily closely tied to the domain model - it is really a part of it, but it is accessed solely through interfaces and/or reflective bean property population and an execute() method. The View component builds immutable object graphs that represent the application model (either live or snapshotted, your choice).

In a servlet based user interface two factories are provided, a View factory and a Control (Command) factory. A typical request will include one or two parts. First part, optional, is a Command to execute. Form data controls relevant information needed by the Command. This can be a direct name to request from the factory, or better, a key to a metadata driven map to retreive the correct command. Said command is populated with the form data (it doesn;t know about the session, the request, servlet state, etc) and executed (or cached in the Session in the case of multi-part wizards). The response (success, failure, exception) is populated into the request out of courtesy (I dislike this part but haven't figure dout how to be rid of it), and the entire shebang is then handed to a View Controller. The View Controller looks at the other part of the request and decides what View should be returned. It is *not* dependent on the Control result (though hooks for failed command execution and Exceptions need to be provided, again this feels like a hack to me).

A typical URI for a request might therefore look like http://www.example.org/do/update-profile/go/user-home?first=John&last=Doe This example is designed to be self explanatory, if it isn't... look at it again more carefully. View control, Command control are all URI specified and are extracted at seperate stages of the request handler. Initial control is given to the Command Controller, which grabs the "update-profile" and the form data, asks the factory for an update-profile action, populates the key-value pairs from the form data to the command object, executes it, handles failure (return to input view for instance), exception (error page), and then hands the request to the View Controller. The View Controller looks at the "user-home" bit, checks its mapping to see what that is (could by dynamic based on what the current user wants his home to be, could simply be home.html, etc) and passes control to whatever View component is specified. The View component knows about the View Factory and can use it to request application model state. ViewFactory.selectAllDoodadsForUser(Session.currentUser) or whatever.

The real benefit of this design is that the application model is truly reusable by any UI that wants to. It can be made remote vie EJB/RMI/SpiffyTechDuJour, it can be handled locally, can do what it wants. Of course the same type of seperation needs to occur to the application model backing store (ir, peristence layer, but I like to think of it as an application contect that persists across server reboots =)

1 writebacks [/src] permanent link