13 October 2009


I have long been an opponent of the Model-View-Controller (MVC) philosophy of user interface design. Okay, it is not a horrible thing and it is certainly a good philosophy to know: keep your programming model separate from the way your users interact with it. The idea is to represent the domain model with objects, present views of said objects and allow a controller to manipulate those objects.

The first question is: Should the "model" be the actual underlying structure of the model or the user's mental model of how the program works? The answer to this question is quite obvious: The model is the actual program model. A user's mental model is not only crude and most likely incorrect, but it most likely varies from user to user. However, since UI design should be user-centric, we must never forget that the user's mental model of the process is important.

On to the View, which provides the user with a representation of the model. The separation allows one to easily add multiple views to the same data model, like viewing data through a pie chart or a bar chart. When Trygve Reenskaug conceived of MVC in 1979, the ability to easily view data in different charts was impressive. Modern users expect so much more! They want the ability to control and manipulate or otherwise interact with the data model. And the view has to be consistent through a desktop GUI application, text interface, an iPhone and Twitter.

This is where the Controller comes in: It is an abstraction of a method to manipulate the model. Unfortunately, even the Pragmatic Programmer will admit that the Controller and View are tightly coupled. This makes a whole lot of intuitive sense, since the things users see should directly relate to the things users do, but I am not sure it ever entered the minds of Xerox employees (I do not blame them, they were pretty busy making copiers and printers).

So we are really stuck with two systems: a Model and a View-Controller. Okay, so there is also Model-View-Presenter, which does not solve any problems, because it is pretty much the same thing (okay, the arrows are pointed a little differently). There has to be a better way to write interface code!

I first considered a new system wherein all data in the system was considered part of the Model, including data for interfaces. A model forms the foundation of information and the processing of that data on a small-scale. From pragmatic philosophy and MVC, the idea is that the model should only interact with what it owns. Data processing and functions therein can only interact with data that they create. For example, a Vector may hold values for X, Y and Z and could have a function named Length, which would represent the length of the vector. However, it should not have an interaction function called DistanceFrom(Vector) : float, which tells the distance between this vector and another. Keep in mind that "interaction" means that objects should not work with things they do not own, but something like a line, which consists of (owns) two points, can validly find the distance between them.

So, that simple example does not really show anything useful, but imagine a more complex system like a bank account transfer. There are rules regarding what the various accounts can and cannot do. For this world, let us say that it is illegal for a BankAccount to have less than $0 or more than $1,000,000 and that all transactions should be logged. We could strap methods to BankAccount like GiveTo(BankAccount) and Recieve(Money), but this leaves unanswered questions, the most imporant of which is: Who is responsible for rolling back on error? Anyone familiar with databases already knows the answer is that this should obviously take place inside of a managed transaction, where the system is responsible for rolling back failures.

A generalization of this transaction system is a Context, which provides a management layer for the data objects to communicate. This answers the second question of our previous problem: the context is responsible for logging the account transfer. This provides a convenient security mechanism as well: since the context is responsible for all interactions, we can easily make a rule that does not allow transfers to occur unless they are logged (and this is especially trivial if the logging system in integrated into the database).

Context provides a layer of abstraction for your objects to communicate through and can be seen as the network of communicating objects in your model. While the model represents things, context represents a system of communication, which begs the question: Why is this layer not named "communication." Not only would it continue to provide the same 'C' letter for an acronym, but it seems to be a better definition. However, using the word "communication" would imply that this layer conveys information, which is not true. While the context-layer provides a means for communication and performs communication by sending messages between systems, it is a stateful management layer - much more than a means of communication.

The final layer is how the context communicates the model to the outside world - the Interface. It is not an interface in the C-family sense of the word; it encompasses all of what you present to the user. An easy mistake is to assume that this only means a graphical- or console-user interface systems, but an interface can be a SOAP or JSON web service, a shared object library, operating system service or whatever method you use to interface with the world outside of your program.

This fits well into a service-oriented architecture, where the model provides local representations of domain objects, the context provides a system for those objects to communicate and an interface to work with the outside world. It is also quite reasonable to expect a context to work with other interfaces aside from the public one your application is presenting. For example, the context of a database-backed system would interact with the interface of an SQL server to provide the data. If the context is properly designed for configuration (Pragmatic Programmer says "Configure, Don't Integrate"), the interface does not need to know where the context gets information from, nor should it matter, and the system will be easily testable and debugable. The next obvious step is to extend this system to realise that we can chain these systems together however we need, so long as we do not let an interface directly manipulate the model (perhaps a better name would be (MC)*M(CI)*CI or the more catchy M(CM)*(CI)+).

But this whole thing was started with talk of end-user interfaces in terms of graphics and consoles. It is a natural and correct assumption that these user interfaces fit into the "interface" component of MCI. The goal is that it should be incredibly simple to swap out a given interface, be it a "system interface" that other system components work with, a "programmer interface" that others are meant to work with or a "user interface" for those regular people. Since they share underlying context components, it should be easy for multiple interface systems to seamlessly interact - changes made through one interface should be automatically reflected in another. However, do not forget that these components are also software. A web application which needs to remember user states or any other packet of information probably needs a model and context to remember it with. Just keep in mind that this should be a different model and context from the data you are serving, since they are fundementally different systems. This is a good design decision, mostly due to the separation of conserns, and will lead to a more service-oriented architecture (which will help when you make the AJAX or mobile version of the site).

Other tips for building data and context? Fill them with as much structural and descriptive metadata as possible. Java annotations (@interfaces) and .NET custom attributes are wonderful for this. In languages that do not support assembly-attached metadata, this functionality can be duplicated with automatically-generated XML files (if you're using Make, MSBuild, Ant or Maven, it is pretty easy to plug in a custom build step) or you could come up with another cool way, so long as semantic information is preserved in a runtime-readable format. This way, it becomes possible to automatically generate user-interface elements based on the program's metadata. Sound to good to be true? In the .NET Windows Forms system, there is a PropertyGrid, which is a single-object editor which designs itself using run-time reflection of attributes from the System.ComponentModel namespace. Java does not have a component like this built-in, but people have made programs in the Java Platform such as L2FProd's JPropertySheet (Apache-lisenced), which duplicates the functionality of PropertyGrid and the Java-Bean-Examiner (GPL-liscenced), which is specialized for EJBs.

There is another architectural pattern which is very similar to the MCI system, which plays off the three-letter acronym (3LA) system of named architectural patterns. It is called Data-Context-Interaction (DCI) and there was a fairly large page dedicated to it in March of 2009. If you are interested, I would highly recommend reading the linked page. Another good read is "The Common Sense of Object-Oriented Programming." Keep in mind that both of these papers quite Squeak-centric (the second, much more so), to the point that it is very difficult to abstract the core concepts out of their papers (and you really have to continually bypass statements like: "Smalltalk does everything for everyone in a clean and efficient system"). If you really like Smalltalk/Squeak, try out BabyIDE, which is a perspective-based IDE for Squeak (those familiar with Eclipse will feel close enough to home).

What is the difference between MCI and DCI? Aside from the different names, not much (I almost prefer "data" over "model" since it more clearly distances it from MVC, but I claim that the first letter of the acronym is much more than simple data). In reality, neither of these patterns are what I would call "mind-blowing." Essentially, it is better-defining the "model" from the MVC pattern by splitting into two parts, the "model" (or "data") and the "context." Furthermore, it gives up on the idea that the "view" and "controller" were ever separate entities by compressing them into an "interface" (or "interaction"). While this is a fairly subtle distinction, but it's the context that makes all the difference in the world.

No comments:

Post a Comment