Monday, November 09, 2009

Simple Dependency Injection using the Unity Framework

My current project is using Unity as an Inversion of Control / Dependency Injection framework.  We’ve had a few challenges and lessons learned while using it.  This is my first post on the topic, and I want to provide a quick look:

Unity is slightly different than most dependency injection frameworks that I’ve seen.  Most dependency injection frameworks, such as Spring.NET (when I last used it), require you to explicitly define the dependencies for your objects in configuration files.  Unity also uses configuration files, but it also can construct any object at runtime by examining its constructor.

Let’s take a simple example where I have a product catalog service that provides simple searching for products by category.  The service is backed by a simple and crude ProductRepository object.

public class ProductCatalog
{
    public ProductCatalog(ProductRepository repository)
    {
        _repository = repository;
    }

    public List<Product> GetProductsByCategory(string category)
    {
        return _repository.GetAll()
                          .Where(p => p.Category == category)
                          .ToList();
    }

    private ProductRepository _repository;
}

public class ProductRepository
{
    public ProductRepository()
    {
        _products = new List<Product>()
            {
            new Product() { Id = "1", Name = "Snuggie", Category = "Seen on TV" },
            new Product() { Id = "2", Name = "Slap Chop", Category = "Seen on TV" }
            };
    }

    public virtual List<Product> GetAll()
    {
        return _products;
    }

    private List<Product> _products;
}

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
}

With no configuration whatsoever, I can query Unity to resolve my ProductCatalog in a single line of code.  Unity will inspect the constructor of the ProductCatalog and discover that it is dependent on the ProductRepository.  Since the ProductRepository does not require any additional dependencies in its constructor, Unity will construct the repository and assign it to the catalog for me.

[Test]
public void CanResolveWithoutConfiguration()
{
    var container = new Microsoft.Practices.Unity.UnityContainer();

    var catalog = container.Resolve<ProductCatalog>();

    var products = catalog.GetProductsByCategory("Seen on TV");

    Assert.AreEqual(2, products.Count);
}

Aside from the fact that this is a crude example, a production system would be extremely limited because the product catalog is tightly coupled to our repository implementation.  Although we could subclass the product repository, a better solution would decouple the two classes by introducing an interface for the product repository.

public interface IProductRepository
{
    List<Product> GetAll();
}

public class ProductRepository : IProductRepository
{
    // ...
}

public class ProductCatalog
{
    public ProductCatalog(IProductRepository repository)
    {
        // ...
    }

    // ...
}

This is a much more ideal solution as our components are now loosely coupled, but Unity no longer has enough information to automatically resolve the ProductCatalog’s dependencies.  To fix this issue, we need to provide this information to the Unity container.  This can be done through configuration files, or by manipulating the container directly:

[Test]
public void CanResolveWithRegisteredType()
{
    var container = new Microsoft.Practices.Unity.UnityContainer();
    
    container.RegisterType<IProductRepository, ProductRepository>();

    var catalog = container.Resolve<ProductCatalog>();

    var products = catalog.GetProductsByCategory("Seen on TV");

    Assert.AreEqual(2, products.Count);
}

Note that while I’m using the Unity container in my tests, it's completely unnecessary. I can simply mock out my dependencies (IProductRepository), pass them in, and test normally.

Conclusion

This demonstrates how Unity is able to resolve simple types without extensive configuration.  By designing our objects to have their dependencies passed in they possess no knowledge of the dependency injection framework, making them lean, more portable and easy to test.

No comments:

Post a Comment