I'd like to buy an argument.
I'm a proponent of isolating application tiers via separate physical assemblies; e.g., MyProject.Core for domain objects and logic vs. MyProject.Web for views. While I do not encounter much push back with this idea, I seem to raise eyebrows with my desire to move ASP.NET MVC controllers to a separate assembly as well. The result of creating separate, physical assemblies for the logical tiers, including the controllers layer, has a number of benefits.
The first and primary reason is that it decreases your ability to shoot yourself in the foot. Let me tell a war story to illustrate. About five years ago I was given the privilege to build the most exciting application of my career thus far. In developing it, we tried to emphasize adding complexity and patterns only while refactoring smells and keeping things as simple as possible (which is almost always the best route to take). We didn't take many prefactoring steps and let the system evolve organically. A painfully learned lesson was putting all of the tiers into one physical assembly and separating them only by namespaces and "team agreement." The major drawback to this was that every layer was technically able to communicate with any other layer, even bi-directionally. (Yes, the most excellent NDepend could assist, but what happens when you have to leave a project in another developer's and/or team's hands for a while?) Inevitably, bi-directional dependencies began to creep into the code. Progressively, the unit tests, for testing the domain layer, started becoming more and more dependent on an up to date test database and working external services; shortcuts began creeping in to the data layer and external services. Accordingly, the unit tests became slower, more fragile, and tedious to maintain. This bleeding of one layer into another had a few ill effects: developers stopped writing and running unit tests (because they were agonizingly slow to run), the application overall became increasingly difficult to maintain and difficult to make modifications without inadvertently affecting various layers simultaneously, and it became almost impossible to upgrade the data layer without touching all of the layers of the application. Yes, I could have done a lot more to prevent some of these problems, both technically and managerially...but I can't say I've learned as much from any other project as well! (A computer science professor of mine once said that you're not an expert until you've made every mistake in the book...that project almost gave me my expert credentials in one fell swoop. Just kidding, I have plenty more mistakes to make. ;)
Some of the other benefits of having physically separated tiers come out of this story as well:
- It's helpful for being able to unit test logical tiers in isolation,
- It's simple to upgrade your data access layer if only the IoC mechanism depends on it directly,
- It's easier to manage a group of developers which can't violate the rules when separated assemblies can only depend on each other in one direction, and
- It's easier to understand (aka - maintain) a tier's responsibilities which have been physically separated from other tiers.
Moral of the story: take necessary prefactoring steps to make applications easier to unit test and maintain...and to make it very hard for you to shoot yourself in the foot!
As a working example, due to these reasons, S#arp Architecture projects have a core domain layer which physically cannot have concrete dependencies on the project's data access layer. Likewise, the controllers layer doesn't have a concrete reference to the data layer or to NHibernate. In addition to separating concerns, this physical layout of the layers, and the direction of dependencies, acts as a form of implicit documentation to the developers how the layers should be used. E.g., YourProject.Controllers knows nothing about the implementation details found within YourProject.Data; YourProject.Core has no concrete clue what the persistence mechanism is; and YourProject.Controllers could care less about which IoC mechanism you're using or what static properties Global.asax.cs might be tracking.
Contrastly (is that a word?), moving the controllers layer into the physical web assembly automatically gives it automatic access to all the references that a web project typically has; references such as your IoC implementation, NHibernate and YourProject.Data. Likewise, mashing any other layers together makes it that much easier for a developer to bleed concerns from one layer into another, inadvertently as it may be. Certainly, there's a lot to be said for discipline, team agreement, and tools such as NDepend to make sure your team is following the rules, but I feel it's beneficial to go a bit further to make it that much more difficult to make a mess of things. When considering the wide breadth of skill levels of developers who may ever be touching a project that you're leading, I'd lean towards enforcing a separation of concerns by default, and make it that much harder for developers to void the warranty. ;)
If you'd like to rebut, it's one pound for a five minute argument, but only eight pounds for a course of ten.
01-09-2009 12:21 PM