High-level modules should not depend on low-level modules, both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
This is not a simple topic to describe in isolation. The Dependency Inversion Principle (DIP) fits in with a combination of multiple patterns, principles and tools. For instance:
- The Strategy Pattern
- The Interface Segregation Principle
- The Single Responsibility Principle
- The Ports & Adapters/Domain Driven Design/Onion design
- Inversion Of Control Containers
- The Hollywood Principle
- The Open/Closed Principle
- The Repository Pattern (when implementing a real system)
To keep this short, this article will try not to involve too many of them. The minimum facts you need to know are:
- Dependency Inversion is the architectural model, Dependency Injection is a method to realize it.
- Your class’s constructor includes the services your class depends on, specified as very abstracted interfaces, rather than your class instantiating them.
- You should have very few ‘new’ statements in the logic core, yes – really.
- Avoid static methods on classes, they prevent mocking and tie you into a concrete implementation.
- Use an Inversion Of Control container (like StructureMap) to make life ‘easier’.
- Think about having a design policy to guide consistency within your team.
- Do not create an interface for low level domain model classes by default.
What’s the business value?
Changing implementation details of dependencies becomes easier for developers, to the point that providing an alternative implementation (e.g. SMS instead of email) could be as simple as dropping in a new assembly and changing a configuration file. The code lends itself well to test automation, and will (generally) be easier to read due to cleaner separation of concerns. However, it requires discipline to maintain a consistent application of the theory. Discovering which class implements an interface is not well supported in the default tooling, so some investment in tools/architecture may be required.
That’s a lot to take in, where do I start?
Before describing how dependency inversion is achieved in C#, there are some fundamental architecture concepts that need to be understood. The traditional three tier model of separation of concerns doesn’t suit the dependency inversion pattern, but a variant of it does, the Onion Architecture. Its benefits and application are best described by Jeffery Palermo’s blog – Onion Architecture, and a summary of developments can be found in The Clean Architecture by Bob Martin.
In summary, the goal is to make a core module be the focus of our application, which all other modules will depend on. The core will define the domain (typically, ‘business objects’) classes and the interfaces they require interaction with. The core will not implement interfaces which are external dependencies. Those must be injected into the core classes by implementing the interface required in a higher layer. This allows strong decoupling of infrastructure items such as third party libraries, the database, configuration files, notification mechanisms (email, push, sms), security, encryption, persistence. This allows changes to implementations of these components with reduced risk to the core and core classes can be surrounded by tests with minimal dependency setup. The ideal situation is that each dependency can be mocked.
How does this differ from a three tier architecture?
A typical 3 tier architecture of a User Interface Layer (UI) instantiating Business Logic Layer (BLL) classes, which in turn instantiate Database Access Layer DAL (or external service classes) is remodelled.
Let’s say in our Onion design, that the BLL is now the core, the lowest point in the dependency chain.
It will contain:
- domain model classes (E.g. User, Basket, Product).
- the abstract definition, as interfaces, of services the core requires from outer layers of the onion, E.g. IUserRepository, ICheckout, IUserQueries, ILogging, INotificationService, some of which might have domain objects as required parameters.
- concrete service classes – providing services that bind these abstract definitions together with rules E.g. LogUserDetails(ILogging, IUserQueries, int currentUserID)
- services which use other services within the same core. Typically represented by interfaces for consistency, but implemented in the core. This over-engineering only benefits testing.
The Data Access Layer will reference the core, thus can use the business objects and logic, which is an inverse of the traditional 3 tier model. One typical overkill at this point is to provide an interface for every core class. That is not absolutely necessary and will cause bloating fast. If you want to see some pictures – head over to Jeffery Palermo’s blog – Onion Architecture.
We are also not treating critical frameworks as dependencies we need to invert. (We do not intend to inject .NET!)
Typical examples of interfaces we might define in core, but implement outside of the core are:
- Persistence interfaces such as databases and configuration files. E.g. IUserRepository, IConfiguration
- Communication (Email, Push, SMS), E.g. INotificationService.
- External Web Services/communication to 3rd party systems E.g. IGridComputing
- System resources e.g. ISystemInfo – we could then test how the code behaves on different platforms.
- Authentication and authorization services
- Thread.Sleep, Random value generation, clock – these might be best used with a function like in this fantastic post. (I believe this is an ambient context pattern).
I found some reasonably good examples here. So why create my own?
Dependency Injection For Dummies – Kevin Pang
The Dependency Inversion Principle – Gabriel Schenker. Although I would say that the DoEncryption method is ripe for being an inverted dependency, we might need different algorithms depending on different export laws.
Layered Architecture, Dependency Injection, and Dependency Inversion – Boodhoo, Jean-Paul S
More on DI…
As mentioned, in a typically three tier architecture: high level modules tend to call low level modules. It’s quite typical for a UI App to ‘new up’ a business logic class and call methods and new up other methods to create dependencies to help it perform its function. The UI becomes the controller for the dependency chain, doesn’t sound right does it? The UI’s responsibility is not just the UI in this scenario. Static methods are then usually created to decouple access to this. In general, static methods prevent mocking and tie code to concrete classes, so aren’t terribly friendly beasts. There is a whole mountain of brittleness with this model. Thus, the DIP and IoC Containers allow us to decouple concretions and plumbing from all layers.
Once we’ve injected all our interfaces into our classes via a constructor, you may think “we have broken encapsulation and information hiding”, by exposing the internals of a class. This was my first reaction too. But conversely, hiding class dependencies makes class interfaces harder to test, they won’t be loosely coupled and they are more ambiguous and not self documenting. With tools like StructureMap, you can automatically create the constructor parameters, so in reality, you do not have to actually call the fully verbose constructor, dulling my objections.
There are a few mechanisms through which you can inject dependencies. Constructor injection is not the only way (although it is usually the lesser of the evils).
+ Classes always valid once constructed
+ Contract is obvious and clean, “to use me, you need X Y and Z”
– Can be ugly
– May also need a default constructor for serialization
– Not all dependencies may have been needed to execute the one method from the class you needed – but that could be a design smell (SRP).
Property (or setter) injection
+ Can tweak the interfaces at any time
– Objects can be an invalid state at any time, more error prone.
– Less intuitive – how do you know if there’s an order, or what minimum set of setters need to be called?
+ Each method has the exact dependency passed.
+ granular & flexible
+ Great if you only have one method that requires this dependency.
– Adds to the method signature, which is brittle
– Can result in huge parameter lists.
– Can be very repetitive (WET)
The Middleton Rule
- Look before you leap! This isn’t something that is trivial to apply after you have finished your project.
- Be consistent with the application of it.
- Apply DIP for testing, but try and have more reasons (architecture, pluggable)
- Use different assembles for each layer
- Don’t create an interface for simple domain objects unless you really need to.
Agile Principles, Patterns, and Practices in C# – Robert C Martin
Onion Architecture by Jeffery Palermo
Domain Driven Design by Eric Evans
Professional Asp.NET Design Patterns – Scott MIllett