Skip to content

Dependency and Control Inversion

Dan Ludwig edited this page Jan 5, 2015 · 3 revisions

If you read the readme.md file on Tripod's homepage, I hope you noticed how much I emphasized fancy terms like Inversion of Control and Dependency Inversion. If you've heard those terms tossed around and aren't really sure exactly what they mean, I have been there too. Let me try to help you understand it the way I do. Really, the ideas behind "Dependency Inversion" and "Inversion of Control" are two sides of the same coin. However, you need to apply the Dependency Inversion principle in your design at compile time in order to be able to achieve Inversion of Control at runtime.

In strongly-typed software projects, dependencies are resolved at compile time. All of your .NET projects will take at least one depdendency on System.dll. Any time you add a tool like JSON.NET or some other nuget package, your project is taking a compile-time dependency. Control flow, on the other hand, happens at runtime. It refers to the order in which code blocks execute. In C#, I often visualize control flowing from the left, to the right, and back to the left while going down.

For example, let's look at the standard flow of control for a typical business application:

  1. Application receives some sort of request message (from a user, a network request, etc).
  2. Application needs to support some kind of special business use case.
  3. Application needs to push/pull data to/from one or more external resources (database, network, email, etc).

Whether you realize it or not, these 3 control flow steps are very closely related to the compile time dependency chain. Typically for the first part, we need to rely on some pre-existing system to provide an entry point for us to receive and begin processing the message. This can come from WPF, WCF, MVC, WebAPI, WebForms, OWIN, Silverlight, etc. Regardless, we begin by writing a code artifact in that context (an ASPX page, Controller, WCF Service, etc). Say we started with an empty MVC project. The code artifact we need to begin with is an MVC Controller with an action method. This provides us with #1 above.

Next we need to provide code which will handle our special business use case. Most Microsoft tutorials you will read out there will tell you to put this code directly in the MVC controller action methods. Whether you do or you don't, the control will still flow from the entry point of the MVC action method into your business use case code. Either way, this flow of control is very closely related to compile time dependencies. Your controller class now depends on your business code in order to function properly. If you factor out the business use case code into a different class under a different namespace, you need to reference that namespace in your MVC controller class. If you refactor that class out into a separate assembly, your MVC project needs an assembly reference (dependency) or it will not compile.

However, this is par for the course: we all know this. Unfortunate things begin to happen when we need to do the 3rd thing, which is to read from or write to some external resource. More often than not, business use cases involve storing data somewhere like a database, or sending email to some people, or passing the data to some other API. When you do that, the control will flow from your business code to some other utility tool like ADO.NET, Entity Framework, NHibernate, System.IO, System.Net, Azure Storage, Redis Cache, Mogno, Couch, Raven, FlavorOfTheMonthDB, etc... I hope you are starting to see the point here.

When control flows from #2 to #3 above, your business code takes dependencies on one or more external resource assemblies -- whatever they may be. Such dependencies couple your business code with that external resource tool. In order to substitute a different tool, you need to resolve all of the dependencies that your business code has on it, and migrate them to support the new tool. Here is the most important takeaway from all of this: the code dependencies point in the same direction as the control flow of the application. Control flows from the code with the most dependencies (#1) to the code which has a medium amount of dependencies (#2) to the code with the fewest dependencies (#3).

##Flipping the Script

Say we started with a web project and split the code among 2 additional assemblies. We let MVC just handle the HTTP-related code necessary to process incoming messages and return models to razor views or produce JSON results. We then create a second assembly project and move all of the business related code into it, and give the MVC project a dependency on it. Finally, we create a third assembly and create some classes in it to perform tool-related tasks that have to deal with lower level details of pulling and pushing data. We then give both of the other projects -- the MVC and the business assembly -- a dependency on this third assembly project. What we have looks like this:

User application --- depends on --> Business assembly --- depends on ---> Tool assembly

The very core of your project is now composed of low-level services. No matter what you do, your business code will always depend on some kind of externalized tooling at compile time. This means none of the code in your tooling assembly can reference any types in your business assembly. So if you wanted to use something like Entity Framework to store data in a database, you could not have code like the following in your Tool assembly:

using (var dbContext = new BusinessDbContext())
{
    return dbContext.Set<BusinessEntity>().ToList(); // won't compile
}

Why not? Because the type BusinessEntity needs to exist in your business assembly! Your tooling assembly has no idea that type exists. Remember, the business code knows about types in the tooling assembly, but the tooling assembly does not know about types in the business assembly. Two assemblies cannot depend on each other... which one would the compiler build first?

Now, let's look at the dependency chain in the Tripod architecture:

User application --- depends on --> Tool assembly --- depends on ---> Business assembly

What we have done here is put the business code at the very core of the project -- it depends on none of our other custom application code (though it will obviously depend on things like System.dll, and possibly other purely computational, non-resource related assemblies). Furthermore, we have not changed the control flow of the application code. Control will still flow from the web project, to the business assembly, to the tool assembly. This is the Dependency Inversion Principle, which is the "D" in the five S.O.L.I.D. software design principles.

How can we make control flow in the opposite direction of the dependency chain? By using an Inversion of Control (IoC) container tool at runtime. Tripod uses SimpleInjector for this, but there are similar tools like Ninject, Castle Windsor, StructureMap, Unity, and others which basically do the same thing. These are not magic tools, though they are so cool that they often seem like it. In reality, they don't actually make control flow in the opposite direction of the dependency chain, at least the compile time dependency chain. What they really do is use reflection to substitute dependencies at runtime.

The key to both of these concepts is object-oriented programming artifact that too many of us ignore way too often: The interface. We will write interfaces to serve as dependencies at compile time, and use an IoC container to substitute real code for those interfaces at runtime.

Interfaces, if you aren't too familiar with them, are software artifacts that allow you to tell code "what can be done", without having to tell it "how it will be done." You can create as many of these as you need to in the business assembly, and whenever you need to do something like access a database or send an email, you can write code that invokes a method on an interface. The compiler doesn't know whether the data will go to SQL Server, Oracle, or MySQL, and it doesn't need to know. It just needs to know "what can be done", and doesn't care about "how it will be one". So your business code ends up depending on an interface, not on an actual class that will be doing the real legwork of accessing the external resource.

After the code is compiled and it is running, the IoC container will substitute an implementation of that interface to the business code so that it can pass the flow of control when necessary. These implementations are defined in the middle assembly. That is where you will write classes that perform the legwork of accessing storage / sending email / whatever. Remember the tooling assembly has a dependency on the business assembly, where the interfaces are defined, so it can see them. When you write the low-level code, you write it so that it conforms to the interface defined in the business project. And after some simple wiring in your IoC container setup code to tell it which class to substitute for which interfaces, control can flow from the core assembly outward to the assemblies which depend on it.

Next: Tripod 101