More Thinking About Model-View-Controller (MVC) And Application Architecture
Ever since my talk about MVC (Model-View-Controller) with Steven Neiland, I've been thinking non-stop about my own application architecture and how to improve it. For the last two weeks, I've been watching presentations and reading articles about domain modeling, MVC, design patterns, and just about anything else that looks relevant. It's both exciting and daunting. I've also been trying to put together some MVC-learning code, but that's a slow process. One of the biggest hurdles in all of this is just figuring out what functionality goes where. I thought sitting down and sketching out the application architecture would help me think through feature distribution.
As of this morning (9:36 AM on June 5th, 2012), here is my current understanding of how an application should generally be put together. I stress the date/time as this understanding may change by the time I am done writing this post! Note that the arrows in this diagram are meant to be general and not exhaustive in nature.
As I've been doing all my reading and watching of materials, I came across a quote from Billy McCaffertythat I think outlines some key ideas that I'm pulling into my application architecture:
... if you're putting any logic into your controller class that you would have to copy and paste, almost verbatim, into, e.g., a WCF service class to replicate the same sort of behavior, but for a different client interface (e.g., WCF vs. MVC), then it's likely a sign that your controller is doing too much and that the code may instead belong in an application service. In a different light, if you're putting anything into the application service layer which would preclude a different client type (e.g., WCF vs. MVC) to leverage it, then you may be introducing controller concerns into the application services. Following these rules of thumb, your controllers remain more appropriately paired with the web tier of your application while your application services provide the true gateway to the rest of the application.
While these concepts have been discussed previously, something in McCafferty's wording makes it very clear for me.
That said, I still have a ton of questions and gray areas. But, keeping this architecture in mind, I'm gaining some ground (hopefully) on the following topics:
Session Management
Since only one client in my above architecture (WWW interface) understands session management, it makes sense that the Controller is the only part of the application that should know about sessions. This means that it is the Controller's job to translate session-data into method-invocation data when communicating with the Service layer.
Spelled-out more bluntly, nothing in your Domain Model should make a reference to Sessions (or URL, or FORM, or CGI for that matter). Doing so may preclude you from ever using a client that doesn't support session management (ex. WebSockets).
Case-in-point:
- The FORM scope doesn't exist during a WSDL-based request.
- The SESSION scope doesn't exist during a WebSocket request. getHttpRequestData() will also fail during a WebSocket request.
Transaction Management
This is still a tricky one for me! At first, I thought all transaction management should be done at the Controller level so that it could tie together atomic actions across multiple entities; however, an application may not actually be using a Database as its persistence layer - it may be using flat files; or, it may not be using any persistence beyond in-memory caching. As such, I think it makes the most sense to move all transaction management out of the Controller and into the Service layer.
This brings up a really interesting question though: Can the service layer make calls back to itself (ie. one service object talking to another)? And, if it can, do we run the risk of accidentally creating nested Transactions?
In ColdFusion 9, you can now have nested transactions without error (the inner-transaction is simply ignored). But, this feels like the wrong "fix." It feels more like a nested transaction should be considered a "code smell" indicating a bad decision.
While I don't have a good answer to this feeling, I have read some things that may be on the right track. Specifically, I've seen people create a divergence between "Application Services" and "Domain Services." While I haven't read too much on it, it seems like the Application services sit above the Domain services and dictate "workflow".
The Application Service layer seems like it would be the perfect place to implement Transaction management, with the caveat that the application service layer represents high-level actions and should probably only be called once per incoming client request. This means that Domain services can talk to each other while Application services cannot.
Data Transfer Format
If the Controller's job is to take incoming request data and pass it off to the Domain Model (via the Service layer interface), what format should the data take? I've seen a number of approaches to this and the one that sits most comfortably with me is to keep this data as low-level as possible. Meaning, don't deal with Domain Entities; rather, pass "dumb" data structures into AND out of the Service layer.
By "dumb", I don't necessarily mean that it has to be Structs and Arrays and Strings. I only mean that it shouldn't be Domain Entities. From what I've read, if you want to create "Form Model" classes that create a Typed data transport, that is fine - so long as you understand that the Form Model may have little to do with the Domain Model.
NOTE: Until I understand this aspect better, I will probably just pass in individual, named arguments to my Service layer methods.
Last night, I came across an Anti-Patterns blog post by Mehdi Khalili that seems somewhat relevant to data transfer. In it, he said something that I thought was very interesting (paraphrase):
The Domain Model should never be in an Invalid state because it doesn't expose state - it only exposes behaviors.
Granted, this was in the context of ORM (Object-Relation Mapping); but, it feels like something I should think more deeply about. If I can put the Domain Model into an invalid state, then perhaps I should consider approaching a problem in a different way.
Gathering View Data
This is another area that is still gray for me. Clearly, the one part of the application that knows about the View is the Controller. As such, the Controller is the one that must gather some data and select a View to render. But, what about parts of the View that don't relate directly to the given request (ex. the Header area of a Master Layout)? Should the Controller gather this data? Or, should the View be able to make requests directly into the Domain Service layer?
I've read points of view on either side that typically come down to Ease of Implementation vs. Ease of Testing. I don't know where I stand on this yet.
When it comes to application architecture, I still have more questions than answers. And, I know there's no "one true way," to putting an application together. But, as always, I like trying to come up with guidelines before I come up with exceptions. More to come!
No comments:
Post a Comment