Layered Architecture, Dependency Injection, and Dependency Inversion
Building loosely coupled application architectures requires more than just separating your application into different layers. In this article, I’ll take a project that was built using techniques that result in fragile, hard-to-test code and introduce some principles, techniques, and refactorings that will help you realize flexibility and testability in your applications.
Most developers understand the value of the layered approach to architecture. The main principle behind layered architectures is that of “separation of responsibility”. Each layer is responsible for a finite amount of work. Any work that cannot (read should not) be done by a particular layer gets delegated to a layer more appropriate for handling the task.
Unfortunately, people using layered architectures can often run into a scenario where they introduce an unnecessary amount of coupling between layers of their application. A high degree of coupling is one factor that can lead to fragile application architectures that are difficult to change or extend.
The first part of this article deals with introducing a design principle that will enable you to take advantage of layered architectures in a much cleaner fashion, and then demonstrates how introducing dependency injection into the mix can help you realize your goals for pluggable application architectures.
The Case at Hand
Assume for a moment that Figure 1 is a screen that I am developing for viewing information on Employees that work with my company. Being that it’s such a “simple” screen, I dive in and quickly hammer out the code shown in Listing 1. (In case anyone asks, I would never do this!)
Figure 1: This looks like a pretty simple screen to build, right?
I’ll pause for a second to allow the utter travesty of that code to truly sink in! The code breaks several application architecture rules:
- Hard-coded connection strings.
- User interface has intimate knowledge of database tables.
- User interface is responsible for mapping database data into domain objects.
- Coding to implementations not abstractions.
- Forget about that last bullet point for a little while. I can start to remedy the first three items by introducing a layered application architecture.
Separating Responsibilities with a Layered Architecture
One of the main issues with the code in Listing 1 is that it takes the single responsibility principle and throws it completely out of the window. The single responsibility principle simply states that “every object should have a single responsibility and therefore only a single reason to change.” If you are already familiar with the term cohesion, you can quickly identify that a component that follows this principle can often be described as a “highly cohesive” component.
If I took a moment to ask the question, “What should the main responsibility of the View Employees Web page be”? The simple answer is that it should only be dealing with the rendering of a set of employees to the user. However, if I take a look at the code-behind, the reality of the situation is quite different. Instead of methods focused around the rendering of employees to the user, the component is currently responsible for:
- Creating a connection to the database.
- Creating a SQL statement to pull the appropriate information from the database.
- Disposing of expensive resources (connection, reader, etc.).
- Mapping the database information to a domain representation of the data.
- Rendering the information to the user.
As you can see from this short list, this component has far too many responsibilities. To make matters worse, not all (most) of the responsibilities have anything to do with “rendering” a list of employees to the user. If this does not smell of low cohesion, I don’t know what does.
For a long time people have been realizing the value of introducing the concept of n-tier/layered architectures into their applications to address this very issue. In short, the introduction of a layered architecture can ensure that each layer (as pragmatically as possible) can adhere to the single responsibility principle. Figure 2 shows a proposed high-level diagram for the separate layers that will make up the application. I am going to tackle refactoring this application by going from the top down. I can first make use of the “passive view” variant of the “model view presenter” design pattern to deal with the abuse of responsibility in the code-behind for the Web page. I will not dive into the details of this pattern in this article; you can take a look at an article I wrote last year that talks about the pattern in more detail: http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/)
Figure 2: A proposed high-level diagram for the separate layers.
The first refactoring I will tackle is to pull out code not directly related to the responsibility of “rendering employees” (read: pull pretty much all code from the code-behind!). Listing 2 shows the resulting code for the code-behind of the Web page. Compared to Listing 1, the difference here is night and day. If you are not familiar with the passive view pattern, here’s a quick description:
- View implementation (Web page) implements an interface that is consumed by the presenter.
- The View interface exposes events that are implemented by the View (Web page) and subscribed to by the presenter.
- The View raises events in response to events that occur on itself (for example, Button is clicked, Load is happening), these events are in turn handled by the presenter.
- The presenter processes the event accordingly and may push information back to the view by means of using the View interface.
- Using the View interface allows the presenter to remain loosely coupled to any particular UI technology (for example, ASP.NET).
Listing 3 shows the first iteration of the presenter class.
From the looks of Listing 3 all that I have managed to accomplish is to push the messiness that was originally located in the code-behind for the Web page and move it into another class. However, with the introduction of the interface for the view, I have introduced a concept that is critical in creating flexible layered architectures: the dependency inversion principle.
By: Jean-Paul S Boodhoo
Jean-Paul S. Boodhoo is a .NET delivery expert who has been working with the .NET Framework since beta 1 of .NET 1.0. He has over seven years of experience in architecting, designing, and developing applications. He spends his days working as an independent consultant; helping teams realize success through agile practices and pragmatic Behavior Driven Development (BDD) techniques.
He has a passion for sharing information on applied behavior-driven development with .NET, and has written articles for Visual Studio Magazine, DevX, and MSDN that utilize BDD to pragmatically apply .NET. Jean-Paul has presented on the popular podcast/screencast .NET Rocks! and DNRTV and has delivered Webcasts for Microsoft on the topic of design patterns in the real world. He is a member of the MSDN Canada Speakers Bureau and a Microsoft Most Valuable Professional (MVP).
You can reach Jean-Paul at firstname.lastname@example.org and he makes continual efforts to update his blog at http://www.jpboodhoo.com/blog.
Software developers use the term cohesion to describe the relation and focus of a particular component.