In my web applications development, I use and love ColdFusion components! However, my use of them is definitely sub-optimal. My approach makes use of a two-tier model consisting of what might loosely be thought of as a Controller and a Gateway layer. This is fine for small applications where not much business logic is repeated. But, in larger applications (which I am starting to build), this approach leads to duplication of core code. Thankfully, Steven Neiland was kind enough to sit down with me at cf.Objective() 2012 and help me understand how I might improve my code with a better MVC (Model-View-Controller) architecture.
Over the course of two days, Steven and I sat down and sketched out some rough domain concepts and System Sequence Diagrams (SSD).
As it turns out, my "controller" layer was really a Frankenstein monster, acting as both the Controller and the Service layer of my domain model; I had it both routing requests and executing critical business logic. My "service" layer, on the hand, was actually a Gateway, doing little more than wrapping database access.
To frame the context of this MVC (Model-View-Controller) exploration, I came up with a scenario that I have at work: I have an application that requires both cookie-based authentication (standard web use) and Header-based authentication (API use). This is an important divide because the cookie-based authentication makes use of ColdFusion session management where as the header-based authentication is session-free (RESTful).
To allow for both of these authentication approaches in the same application, the Service layer must remain session-agnostic. This constraint means that it must be the Controller's job to translate session data into request data that can be explicitly passed into Service layer for execution of business logic.
I know there's never "one" way to do something; but coming up with general guidelines, like the one above, helps me to wrap my head around how all of the parts fit together. After my discussions with Steven, here's my basic understanding of the three-tier model:
Controller - The "C" in "MVC"
Red Flags: My Controller architecture might be going bad if:
- The Controller makes too many requests to the Service layer.
- The Controller makes a number of requests to the Service layer that don't return data.
- The Controller makes requests to the Service layer without passing in arguments.
If the above "red flag" conditions start to happen, then I probably have a Controller layer that is doing too much. I should probably factor the logic out into a more cohesive Service layer that can be invoked by the Controller. Furthermore, if the Controller is making requests to the Service layer without passing in arguments, then I may have a Service layer that is breaking encapsulation (ex. referring to the Session, CGI, FORM, or URL scopes).
View - The "V" in "MVC"
The View's job is to translate data into a visual rendering for response to the Client (ie. web browser or other consumer). The data will be supplied primarily by the Controller; however, the View may also have a helper that can retrieve data that is associated with the rendering and not necessarily with the current request (ex. aside data, footer data).
Red Flags: My View architecture might be going bad if:
- The View contains business logic.
- The View contains session logic.
If the above "red flag" conditions start to happen, then I probably have a View layer that is doing too much. Business logic should be factored out into a Service object. And, session references should probably be factored out into a Helper object or a Controller. This will make the Views easier to build and to test since they rely solely on data and not on an entire application being in place.
NOTE: View rendering is something that I am still fuzzy on. My current approach uses a CFM-only approach to rendering; as such, integrating Components into a view rendering lifecycle is still something that I need to think about.
Model - The "M" in "MVC"
The Model's job is to represent the problem domain, maintain state, and provide methods for accessing and mutating the state of the application. The Model layer is typically broken down into several different layers:
- Service layer - this layer provides cohesive, high-level logic for related parts of an application. This layer is invoked directly by the Controller and View helpers.
- Data Access layer - (ex. Data Gateway, Data Access Object) this layer provides access to the persistence layer. This layer is only ever invoked by Service objects. Objects in the data access layer do not know about each other.
- Value Objects layer - this layer provides simple, data-oriented representations of "leaf" nodes in your model hierarchy.
Red Flags: My Model architecture might be going bad if:
- The Model contains session logic.
- The Value Objects retain (ie. store) references to Service objects or Gateway objects.
If the above "red flag" conditions start to happen, then I probably have a Model layer that is breaking encapsulation. By letting the service layer refer to sessions, I am also creating a Model that is hard to test without having an entire application in place. Furthermore, having session references in the model makes the model incredibly hard to reuse across different controllers (as per my web/API split mentioned earlier).
A huge thanks to Steven Neiland who took the time to sit down with me and help me understand this stuff. I know there's never enough time at Conferences like cf.Objective() to talk to everyone; as such, I'm very thankful that he took a good amount of time to talk to me.