Developing Component-Driven Applications powered by IoC Containers
This article continues talk on the topic of Component-Driven Development with IoC Containers. We'll expand on the topic of the actual development process of new applications and migrating existing legacy applications.
Developing New Applications
In the ideal-world situation with established component libraries for the domain and an IoC ready to provide infrastructure, delivering an application becomes a pleasurable experience. Yet, this is rarely the case when a new project starts.
In reality we neither have the actual components nor the project-specific knowledge required for rolling out the stable component library right in the first phase. Experience and shared libraries from similar projects may provide a good start, but they still will not be able to get you all the way to the delivery.
This is somewhat similar to developing a syntax for some new language - it is impossible to foresee and model all key words and syntax elements for the specific domain without a plethora of actual use cases and some iterations.
So the only working way is about evolution, where design comes first and application evolves from top to the down (as we learn about efficient ways to reflect domain knowledge into the development realm of reusable components).

Let's walk over the simplified development process.
First, as it usually happens in any project being initiated properly, we define what business functionality has to be delivered by the application (and then refine the definition a few times with all the major project stakeholders).
Second, we implement this functionality by developing against some high-level service contracts, which are defined in the very process. A few short prototyping sprints might be needed here before we get a satisfactory design to proceed with. Deliverables of this step are interface specifications for the components within the service layer (prototypes are discarded).
Side note. Best results on this stage are obtained, when a single architect is capable of reimplementing the entire application in his mind, while evolving it to the best design within multiple iterations. Quality of the deliverable depends on the detalization level of the design, that a person can hold in his mind, while working it through and evolving. In this situation numerous real-world iterations (drawing away valuable and scarce development resources) could be saved at the cost of some head ache.
Third, we create mock implementations for these services, hiding them inside the IoC Container.

At this step we already have got a real application (although completely based on mocked services) that could be used for the demo and presentation purposes. This allows us to start receiving feedback from the involved people. At this point we can switch from the pure prototyping and project management approach to more flexible iterative development. Continuous integration and automated deployment services will have to be established within the development environment in order to achieve this, while reducing development friction.
Then, we implement actual service layer below high-level service contracts, gradually replacing mock classes with the actual implementations. These actual implementations may (and probably will) require help from some other service contracts from lower levels (which would be implemented right away, mocked or implemented in a simple form).

It is really common to discover some unforeseen interactions, threats or opportunities in the process, that will require for the top-level service contracts to evolve along with their implementations. This brings some sort of change shock, that originates at some point of the application logic and affects related components, requiring their redesign (or even change in service contracts).
However, since components handle fine-grained responsibilities, while being loosely coupled by the IoC , such a shock usually does not go far in the infrastructure, usually being absorbed at the service contract boundaries. That's what makes the difference between "Oh, we've got to rewrite 50% of the code in order to implement this feature" and "Simple, we've just got to isolate this concern and swap the implementation".
We work this way all the way to the bottom of the application, gradually stabilizing higher levels and evolving lower levels. Cross cutting-concerns are injected into the architecture along the process.

In the end we might end up with an application that works and is flexible enough for the planned changes. Yet, this may not be the end of the evolution. Good components and even infrastructure bits tend to be reused in other similar applications (just because it is so easy to do that). This brings new constraints and requirements, creating new change shocks that require further evolution of the domain components.
Good separation of concerns between components and logical component layers, their responsible reuse (sometimes it is better not to reuse in order to avoid unnecessary complexity) and a lot of planning can help to absorb these shocks, while letting the application to grow further, efficiently adapting to further changes in business requirements.
Migrating Legacy Applications
Unfortunately, greenfield scenario is not the most common in the world. More often we encounter situations, when existing legacy application can't evolve any more due to being strangled with manual wiring and plumbing code (let alone the complexity of the hard-coded interactions). Yet, these legacy applications happen to have a lot of business value and must be adapted to new business requirements.
One of the options of loosing up the tension is to gradually introduce IoC and component infrastructure. Since this is not a complete redesign, application has to stay more-or-less production ready most of the time (although short destabilization spikes are allowed, as long as they are known and handled). So we can't mess with the top logical layers that represent business functionality (we would not be able due to the complexity anyway) and we'll have to evolve it from bottom to the top.
At this point we are considering that continuous integration is established to enforce consistently high quality of the new changes being introduced, while providing us with some protection against quality regressions that are likely to happen in the legacy codebase.
We might start evolution in the lowest logical section of the application - in repository and DAL layers. We do that by abstracting the functionality behind the service contracts that are implemented by the components.
At this point of time we do not have any IoC infrastructure in our application yet. So for the evolution process it is allowed to introduce injection points via the static service locators. However, as the evolution process goes, these static service locators would move higher and higher in the application logic till they either hit infrastructure limitations (i.e. resolving components in the web services) or get finally settled down in the places they belong to (application hosts).

Temporary static service locator might look like this in .NET (implementation for the Autofac IoC Container):
public static class GlobalSetup
{
public static readonly IResolver Resolver;
// this is fired when container is accessed first time
static GlobalSetup()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new ConfigurationSettingsReader());
var container = builder.Build();
// create IoC-decoupled resolver
Resolver = new Resolver(
t => container.Resolve(t),
(t, s) => container.Resolve(s));
}
}
Where Resolver merely implements following IResolver interface via the lambda parameters:
public interface IResolver
{
// Methods
TService Get<TService>();
TService Get<TService>(string name);
}
You could look up its implementation in the Lokad Shared Libraries, but its really trivial.
When legacy code uses components directly, we could reference their service contracts like:
var repository = GlobalSetup.Resolver
.Get<ICustomerRepository>();
When we don't control the resolution point (i.e.: this happens with .NET-instantiated classes like web services or object data sources), we could use simple proxy classes. These classes bring control of the component instantiation back into our hands and could probably be created with your favorite refactoring tool in less than in a minute.
Sample IoC proxy class for ICustomerRepository might look like:
public sealed class CustomerProxy : ICustomerRepository
{
private readonly ICustomerRepository _repository;
public CustomerProxy()
{
_repository = GlobalSetup.Resolver
.Get<ICustomerRepository>();
}
public Guid CreateCustomer(Customer customer)
{
return _repository.CreateCustomer(customer);
}
public Customer GetCustomer(Guid customerID)
{
return _repository.GetCustomer(customerID);
}
}
In order to quickly create such a component proxy with Visual Studio 2008 and R# we merely have to code the class declaration with the constructor:
public sealed class CustomerProxy : ICustomerRepository
{
private readonly ICustomerRepository _repository;
public CustomerProxy()
{
_repository = GlobalSetup.Resolver
.Get<ICustomerRepository>();
}
// place cursor here
}
Then place cursor below constructor and call for R# autogeneration popup menu (Alt+Ins for me). There we select "Generate | Delegating Members", hit [Space] to select everything and then [Enter] to generate. That's it.

The technique also works with the situations, where proxy has to implement multiple interfaces at once (i.e.: web service aggregating a few service contracts).
Another trick helps out, when you get complex component dependencies in your solution. Automatic IoC/DI visualization might help you to regain understanding of the domain:

There are a few more tips and tricks on developing components in for IoC infrastructure and enforcing common design guidelines upon them. So there might eventually be a third article in the series some time later.
Meanwhile you can:
- Subscribe to the journal RSS feed to stay tuned for the updates.
- Share you thoughts about this article either in comments or privately.
- Do you have a real world experience with component-driven development in complex projects? I'd love to hear about that!
Related articles:
Thursday, June 4, 2009 at 1:53
Reader Comments (1)
It's interesting to see the emphasis on substituting components for the purpose of evolutionary development, rather than just testing. I've worked this way before and found it's very effective.
Excellent insights into 'refactoring to IoC' as well - I've never thought of it in terms of moving through layers - I like the model!
Nick