It's common to hear developers promote layering as a means to provide extensibility. The most common example, and one I used in Part 2 when we looked at interfaces, is the ability to switch out your data access layer in order to connect to a different database. If your projects are anything like mine, you know upfront what database you're going to use and you know you aren't going to have to change it. Sure, you could build that flexibility upfront - just in case - but what about keeping things simple and You Aren't Going To Need IT (YAGNI)?
I used to write about the importance of domain layers in order to have re-use across multiple presentation layers: website, windows applications and web services. Ironically, I've rarely had to write multiple front-ends for a given domain layer. I still think layering is important, but my reasoning has changed. I now see layering as a natural by-product of highly cohesive code with at least some thought put into coupling. That is, if you build things right, it should automatically come out layered.
The real reason we're spending a whole part on decoupling (which layering is a high-level implementation of) is because it's a key ingredient in writing testable code. It wasn't until I started unit testing that I realized how tangled and fragile my code was. I quickly became frustrated because method X relied on a functional class Y which needed a database up and running. In order to avoid the headaches I went through, we'll first cover coupling and then look at unit testing in the next part.
(A point about YAGNI. While many developers consider it a hard rule, I rather think of it as a general guideline. There are good reasons why you want to ignore YAGNI, the most obvious is your own experience. If you know that something will be hard to implement later, it might be a good idea to build it now, or at least put hooks in place. This is something I frequently do with caching, building an ICacheProvider and a NullCacheProvider implementation that does nothing, except provide the necessary hooks for a real implementation later on. That said, of the numerous guidelines out there, YAGNI, DRY and Sustainable Pace are easily the three I consider the most important.)
Sneak Peak at Unit Testing
Talking about coupling with respect to unit testing is something of a chicken and egg problem – which to talk about first. I think it's best to move ahead with coupling, providing we cover some basics about unit testing. Most importantly is that unit tests are all about the unit. You aren't focusing on end-to-end testing but rather on individual behavior. The idea is that if you test each behavior of each method thoroughly and test their interaction with one and other, you're whole system is solid. This is tricky given that the method you want to unit test might have a dependency on another class which can't be easily executed within the context of a test (such as a database, or a web-browser element). For this reason, unit testing makes use of mock classes – or pretend class.
Let's look at an example, saving a car's state:
public class Car
{
private int _id;
public void Save()
{
if (!IsValid())
{
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid state");
}
if (_id == 0)
{
_id = DataAccess.CreateInstance().Save(this);
}
else
{
DataAccess.CreateInstance().Update(this);
}
}
private bool IsValid()
{
//todo: make sure the object is in a valid state
return true;
}
}
To effectively test the Save method, there are three things we must do:
- Make sure the correct exception is thrown when we try to save a car which is in an invalid state,
- Make sure the data access' save method is called when it's a new car, and
- Make sure the Update method is called when it's an existing car.
What we don't want to do (which is just as important as what we do want to do), is test the functionality of IsValid or the data access' Save and Update functions (other tests will take care of those). The last point is important – all we want to do is make sure these functions are called with the proper parameters and their return value (if any) is properly handled. It's hard to wrap your head around mocking without a concrete example, but mocking frameworks will let us intercept the Save and Update calls, ensure that the proper arguments were passed, and force whatever return value we want. Mocking frameworks are quite fun and effective....unless you can't use them because your code is tightly coupled.
Not ALL coupling is bad
In case you forgot from Part 1, coupling is simply what we call it when one class requires another class in order to function. It's essentially a dependency. All but the most basic lines of code are dependent on other classes. Heck, if you write string site = "CodeBetter", you're coupled to the System.String class – if it changes, your code could very well break. Of course the first thing you need to know is that in the vast majority of cases, such as the silly string example, coupling isn't a bad thing. We don't want to create interfaces and providers for each and every one of our classes. It's ok for our Car class to hold a direct reference to the Upgrade class – at this point it'd be overkill to introduce and IUpgrade interface. What isn't ok is any coupling to an external component (database, state server, cache server, web service), any code that requires extensive setup (database schemas) and, as I learnt on my last project, any code that generates random output (password generation, key generators). That might be a somewhat vague description, but after this and the next part, and once you play with unit testing yourself, you'll get a feel for what should and shouldn't be avoided.
Since it's always a good idea to decouple your database from your domain, we'll use that as the example throughout this part.
Dependency Injection
In Part 2 we saw how interfaces can help our cause – however, the code provided didn't allow us to dynamically provide a mock implementation of IDataAccess for the DataAccess factory class to return. In order to achieve this, we'll rely on a pattern called Dependency Injection (DI). DI is specifically tailored for the situation because, as the name implies, it's a pattern that turns a hard-coded dependency into something that can be injected at runtime. We'll look at two forms of DI, one which we manually do, and the other which leverages a third party library.
Constructor Injection
The simplest form of DI is constructor injection – that is, injecting dependencies via a class' constructor. First, let's look at our DataAccess interface again and create a fake (or mock) implementation (don't worry, you won't actually have to create mock implementations of each component, but for now it keeps things obvious):
internal interface IDataAccess
{
int Save(Car car);
void Update(Car car);
}
internal class MockDataAccess : IDataAccess
{
private readonly List<Car> _cars = new List<Car>();
public int Save(Car car)
{
_cars.Add(car);
return _cars.Count;
}
public void Update(Car car)
{
_cars[_cars.IndexOf(car)] = car;
}
}
Although our mock's upgrade function could probably be improved, it'll do for now. Armed with this fake class, only a minor change to the Car class is required:
public class Car
{
private int _id;
private IDataAccess _dataProvider;
public Car() : this(new SqlServerDataAccess())
{
}
internal Car(IDataAccess dataProvider)
{
_dataProvider = dataProvider;
}
public void Save()
{
if (!IsValid())
{
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid state");
}
if (_id == 0)
{
_id = _dataProvider.Save(this);
}
else
{
_dataProvider.Update(this);
}
}
private bool IsValid()
{
//todo: make sure the object is in a valid state
return true;
}
}
Take a good look at the code above and follow it through. Notice the clever use of constructor overloading means that the introduction of DI doesn't have any impact on existing code – if you choose not to inject an instance of IDataAccess, the default implementation is used for you. On the flip side, if we do want to inject a specific implementation, such as a MockDataAccess instance, we can:
public void AlmostATest()
{
Car car = new Car(new MockDataAccess());
car.Save();
if (car.Id != 1)
{
//something went wrong
}
}
There are minor variations available – we could have injected an IDataAccess directly in the Save method or could set the private _dataAccess field via an internal property – which you use is mostly a matter of taste.
Frameworks
Doing DI manually works great in simple cases, but can become unruly in more complex situations. A recent project I worked on had a number of core components that needed to be injected – one for caching, one for logging, one for a database access and another for a web service. Classes got polluted with multiple constructor overloads and too much thought had to go into setting up classes for unit testing. Since DI is so critical to unit testing, and most unit testers love their open-source tools, it should come as no surprise that a number of frameworks exist to help automate DI. The rest of this article will focus on StructureMap, a Dependency Injection framework created by fellow CodeBetter blogger Jeremy Miller. (http://structuremap.sourceforge.net/)
Before using StructureMap you must configure it using an XML file (called StructureMap.config) or by adding attributes to your classes. The configuration essentially says this is the interface I want to program against and here's the default implementation. The simplest of configurations to get StructureMap up and running would look something like:
<StructureMap>
<DefaultInstance PluginType="CodeBetter.Foundations.IDataAccess, CodeBetter.Foundations"
PluggedType="CodeBetter.Foundations.SqlServerDataAccess, CodeBetter.Foundations" />
</StructureMap>
While I don't want to spend too much time talking about configuration, it's important to note that the XML file must be deployed in the /bin folder of your application. You can automate this in VS.NET by selecting the files, going to the properties and setting the Copy To Ouput Directory attribute to Copy Always. (There are a variety of more advanced configuration options available. If you're interested in learning more, I suggest the StructureMap website).
Once configured, we can undo all the changes we made to the Car class to allow constructor injection (remove the _dataProvider field, and the constructors). To get the correct IDataAccess implementation, we simply need to ask StructureMap for it, the Save method now looks like:
public class Car
{
private int _id;
public void Save()
{
if (!IsValid())
{
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid state");
}
IDataAccess dataAccess = ObjectFactory.GetInstance<IDataAccess>();
if (_id == 0)
{
_id = dataAccess.Save(this);
}
else
{
dataAccess.Update(this);
}
}
private bool IsValid()
{
//todo: make sure the object is in a valid state
return true;
}
}
To use a mock rather than the default implementation, we simply need to inject the mock into StructureMap:
public void AlmostATest()
{
ObjectFactory.InjectStub(typeof(IDataAccess), new MockDataAccess());
Car car = new Car();
car.Save();
if (car.Id != 1)
{
//something went wrong
}
ObjectFactory.ResetDefaults();
}
We use InjectStub so that subsequent calls to GetInstance return our mock, and make sure to reset everything to normal via ResetDefaults.
DI frameworks such as StructureMap are as easy to use as they are useful. With a couple lines of configuration and some minor changes to our code, we've greatly decreased our coupling which increased our testability. In the past, I've introduced StructureMap into existing large codebases in a matter of minutes – the impact is minor.
Conclusion
Reducing coupling is one of those things that's pretty easy to do yet yields great results towards our quest for greater maintainability. All that's required is a bit of knowledge and discipline – and of course, tools don't hurt either. It should be obvious why you want to decrease the dependency between the components of your code – especially between those components that are responsible for different aspects of the system (UI, Domain and Data being the obvious three). In the next part we'll look at unit testing which'll really leverage the benefits of dependency injection. If you're having problems wrapping your head around DI, take a look at my more detailed article on the subject at http://dotnetslackers.com/articles/designpatterns/IntroducingDependencyInjectionFrameworks.aspx.
Posted
Mon, Dec 10 2007 8:22 PM
by
karl