Separation of Concerns by example: Part 5
In our last example, disaster finally struck our quaint little application. A strange defect showed up, which would be almost impossible to reproduce back on our developer machine. But because we’ve broken out our dependencies, our CustomerFinder became easier to test. When it became easier, actually possible to test, we were able to reproduce the circumstances of the bug.
With the dependencies broken out, we were able to push in fake versions through the constructor. With our behavioral specifications in place, guarding us against this bug in the future, we can have complete confidence that this bug is fixed now and forever.
Other parts in this series include:
- Separation of Concerns – how not to do it
- Separation of Concerns by example: Part 1 – Refactoring away from static class
- Separation of Concerns by example: Part 2 – Specialized interface for Cache
- Separation of Concerns by example: Part 3 – Creating the repository
- Separation of Concerns by example: Part 4 – Fixing a bug with unit tests
There’s still one last bothersome piece of our application, the construction of the CustomerFinder. We split out the dependencies quite nicely, and told the clients of the CustomerFinder exactly what is needed for the class to function:
public class CustomerFinder { private readonly ICustomerRepository _customerRepository; private readonly ICustomCache _customCache; public CustomerFinder(ICustomerRepository customerRepository, ICustomCache customCache) { _customerRepository = customerRepository; _customCache = customCache; }
However, now it’s on the burden of the client to find the right implementations of the dependencies to plug in. The problem is, clients often don’t care what gets plugged in, they too only care that they get a CustomerFinder. Sure, some users of this class might want some different implementation, but by and large, we’d like to encapsulate selection of dependencies away from CustomerFinder clients.
We have quite a few options for this complex construction:
- An overloaded constructor that picks the right instances
- A static creation method (CustomerFinder.CreateInstance())
- A separate factory class
- Dependency injection with an Inversion of Control container
All of these are valid options with their own advantages and disadvantages. However, the last option allows some interesting benefits, such as changing implementations out from configuration at runtime. Suppose we want to see how our application runs when the database is slow? Or a certain external service is down, slow, or not responding?
Preparing for StructureMap
Here’s the offending code we’d like to fix:
public class CustomerManager { [DataObjectMethod(DataObjectMethodType.Select, false)] public static Customer[] GetCustomers(int startRowIndex, int maximumRows) { var finder = new CustomerFinder(new CustomerRepository(), new CustomCache()); return finder.FindAllCustomers(startRowIndex, maximumRows); } }
Everything to the right of the “var finder”, I’d like to replace with…something else. But first, I want to remove the dependency from CustomerManager to the specific CustomerFinder implementation. After all, the CustomerManager doesn’t really care about this specific CustomerFinder, but rather just something that finds customers. Let’s perform the Extract Interface refactoring on CustomerFinder, by first creating “something that finds customers”, an ICustomerFinder:
public interface ICustomerFinder { Customer[] FindAllCustomers(int startRowIndex, int maximumRows); }
With ReSharper, this refactoring is just a couple of keystrokes away. Otherwise, I’ll:
- Create the ICustomerFinder interface
- Copy the signature of the public methods I want in the interface, and paste them in the ICustomerFinder interface
- Make the CustomerFinder implement the ICustomerFinder interface
The last part is easy, just add a little section after the class declaration:
public class CustomerFinder : ICustomerFinder
I don’t have to change anything about the CustomerFinder class other than this part.
Finally, I’ll change the CustomerManager method to remove the “var” keyword, making it explicit what the CustomerManager is trying to use:
[DataObjectMethod(DataObjectMethodType.Select, false)] public static Customer[] GetCustomers(int startRowIndex, int maximumRows) { ICustomerFinder finder = new CustomerFinder(new CustomerRepository(), new CustomCache()); return finder.FindAllCustomers(startRowIndex, maximumRows); }
The GetCustomers method now uses an ICustomerFinder, but creates the whole CustomerFinder mess. Now we’re ready to introduce the fancy factory, StructureMap.
Introducing StructureMap to the mix
With our CustomerManager (the class used by an ObjectDataSource to fill a web control) using only an ICustomerFinder, we can concentrate on the right part, the wiring up of dependencies. We have several options to configure the correct dependencies:
- XML
- Attributes
- Code
After using StructureMap for some time, I find the code option tends to be the easiest to maintain, because:
- It’s easy to edit
- It plays nice with refactoring tools
- It’s not XML
- None of my classes become “infrastructure-aware”
- All setup is in one place
With attributes, I can decorate my classes and interfaces, but the configuration is in at least two places with each class. Sometimes, I don’t have access to the underlying interface, so I can’t go back and put an attribute on it.
And XML is well, XML, and therefore automatically more difficult to maintain. No Intellisense for class names etc. means that typos and such cause problems more often.
Finding a home
Now that we’ve decided on the code route, where should this code go? We need to put all the configuration in the application startup part. Since we’re using ASP.NET, this means the Global.asax is a good candidate. We’ve already downloaded the latest StructureMap release (2.4.9) and added the reference to our ASP.NET project, now we just need to fill it in.
The basic idea behind our configuration is that we need to tell StructureMap what the interfaces and concrete types are. Whenever someone asks StructureMap for an instance of a specific type, that type along with all of its dependencies need to be configured. This means ICustomerFinder, ICustomCache and ICustomerRepository, along with all of the concrete types.
Here’s what I came up with:
public class Global : HttpApplication { protected void Application_Start(object sender, EventArgs e) { StructureMapConfiguration .ForRequestedType<ICustomerFinder>() .TheDefaultIsConcreteType<CustomerFinder>(); StructureMapConfiguration .ForRequestedType<ICustomCache>() .TheDefaultIsConcreteType<CustomCache>(); StructureMapConfiguration .ForRequestedType<ICustomerRepository>() .TheDefaultIsConcreteType<CustomerRepository>(); } }
I put all of my configuration in the Global.asax code-behind, in the Application_Start event. That way the configuration happens only once at application startup.
Using the StructureMapConfiguration class, I tell StructureMap each requested type and the concrete type that should be injected. When someone asks for the ICustomerFinder, StructureMap will create the “real” type of CustomerFinder. But remember that CustomerFinder only had one constructor that took two other dependencies:
public CustomerFinder(ICustomerRepository customerRepository, ICustomCache customCache) { _customerRepository = customerRepository; _customCache = customCache; }
But since each of these two constructor argument types are also configured in StructureMap, StructureMap is smart enough to wire the whole thing up for us. Notice we didn’t tell StructureMap what to put in specifically into the constructor, it just figured the whole object graph out.
Now that StructureMap’s configured, let’s go back to the CustomerManager class to fix that constructor mess.
Instantiating the component
With StructureMap, creating an instance configured in StructureMap is a cinch:
[DataObjectMethod(DataObjectMethodType.Select, false)] public static Customer[] GetCustomers(int startRowIndex, int maximumRows) { ICustomerFinder finder = ObjectFactory.GetInstance<ICustomerFinder>(); return finder.FindAllCustomers(startRowIndex, maximumRows); }
That’s it, just the one call to ObjectFactory.GetInstance. Notice I only used the interface type, and not the concrete type. Our CustomerManager only really cares about finding customers, not about who actually does the finding. All it wants to do is ask StructureMap, “Give me something (ICustomerFinder) that can find customers”. CustomerManager doesn’t need to care about the concrete CustomerFinder, the caching business or the repository.
This leads us to a very flexible separation of concerns. No longer are our classes dependent on specific implementations, but only interfaces that expose a cohesive set of operations.
Once we have the constructor business out of the way, a quick view at our web applications confirms that the dependencies are wired up correctly.
Wrapping it up
We’ve come a long way since our original example. The original application had three “tiers”, split between an ASPX page, a static method, and LINQ to SQL. Only one of these classes was something we created, the CustomerManager.
But this class still had too many responsibilities, which would have given us trouble when we needed to change it. Now we have many more classes (4 vs. 1) and interfaces (3 vs. 0). For those who don’t like more classes, GET OVER IT. High cohesion and less coupling means smaller classes with fewer, tighter concerns. More classes with high cohesion is far easier to maintain, since the responsibilities of each class can be easily discerned. I’ve seen far too many one-method classes with thousands of lines in the one method to convince me that more classes is better.
With separation of concerns in place, along with dependency inversion (dependencies given to the class through the constructor), the responsibilities and dependencies are easy for both clients of the class and maintainers of the class to figure out. Someone looking at the CustomerFinder class can immediately see it needs a backing store and something with caching to function properly.
When we ran into a gnarly bug, we were able to diagnose, fix, and lock down the correct behavior with BDD-style behavioral specifications. As long as our specs are part of a continuous integration process, we can have confidence that the correct behavior won’t be undone.
Separation of concerns is easier with TDD, but TDD won’t separate the concerns for us. It only shows us where the friction and smells are, and it’s up to us to decide whether or not to act upon them. There have been plenty of times I run into a class with too many responsibilities, but can’t decide how to break it up. Later, when more responsibility needs to be added, the picture becomes clear(er) and I then break it up.
In the end, we care about developing for two people: the people using our software and the people maintaining our software. Separation of concerns leads to higher maintainability, as we saw in this series. With high maintainability, we’re able to satisfy both camps, as we can change software at a faster, continuous pace.