/// Tuesday, April 11

Thinking Dependency Injection

I'm creating components to convert proprietary source files into a standardized XML format. The legacy backend business components get their settings from disparate sources. Some settings are retrieved via a custom configuration object while others are read using stored procedures. Writing a unit test requires knowledge of how to retrieve those settings.

Point 1 - Components should be focused on business logic not infrastructure logic.

Here's a contrived example.

class XmlConverter {
    void Convert(int orderId)
    {
        // get settings from a database
        DbSettings dbSettings = GetDbMetadata(orderId);

        // get from config object
        string targetFile = OurConfig.GetPath("StandardStore") + dbSettings.OrderNumber + ".xml";
        
        string sourceXmlFile = dbSettings.XmlFile;
        string stylesheet = dbSettings.Stylesheet;
            
        string standardXml = XmlUtils.XslTransform(sourceXmlFile, stylesheet);
        
        using (TextWriter tw = new StreamWriter(targetFile))
        {
            tw.Write(standardXml);
        }
    }
}

The preceding code looks normal for most of us. And if you are the developer who wrote the code you know how to setup the database and you could select a valid order id so everything works when you test it. In addition, you know how the configuration object works and you can change its value blindfolded. It's so darn easy. Or is it?

Along comes poor me. I'm assigned to the project to fix an issue with the stylesheet. I hold a black-belt in stylesheet code-fu. Puh-leeze, I'll knock this task out in less than an hour. Then, why did it take me all day? Well because all that information you have in your head is too much to grasp for someone new to a project. The object knows too much. You know too much.

Point 2 - Objects should be a on a need-to-know basis.

How do we fix this with dependency injection? First, think backwards. Give the component the information it needs, instead of that component retrieving what it needs. That is the basic paradigm shift of Dependency Injection. A component is injected with the dependencies it needs. Let's rewrite this.

class XmlConverter {
    void Convert(string sourceXmlFile, string stylesheet, string targetFile)
    {
        string standardXml = XmlUtils.XslTransform(sourceXmlFile, stylesheet);
        
        using (TextWriter tw = new StreamWriter(targetFile))
        {
            tw.Write(standardXml);
        }
    }
}

Now the component is focused on what it has to do---converting a file and saving it to some known location. I can go in there and fix the stylesheet in 15 minutes and everyone is happy. What about configuration? First, let's look at the advantage of what just happened. To write a unit test, I simply pass in source XML file, a stylesheet file and the absolute path to where the transformed file is persisted. If everything works, I'm 99.9% sure my fix will close the ticket. I didn't have to know about infrastructure nor could I have affected it.

What about the settings? Someone has to retrieve it and inject it into the object. If you're the original coder, you would have already done this. If you're a maintainer, don't worry about it. You don't need to know to do your job. The infrastructure code is separate from business component logic. The application is easy to maintain.

Dependency Injection containers help in this effort by allowing you to wire objects together declaritively. For example, why build a SqlConnection object when you can ask the container for pre-built one?

SqlConnection connection = Container.GetObject("sqlconnection");

The container will cache it, or return a singleton or create a prototype when asked. Object instantiation is controlled declaritively either through a configuration file or a compiled script. The same kind of things you're doing now. Now it's done in one place and you don't have to build any infrastructure code. Use the container.

Comments:

Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?