In my last posts I have talked about using assembly references to preserve critical design aspects. In Enforce Consistency with Assemblies References I talk about how we can use references to outline the allowed dependencies in code and how to use a references diagram to discover code at wrong levels of abstractions. In Separating Data Access Concern I show how we can enforce separation of concerns by using references and I detail this with the data access example. In this post I will talk about the relation between assembly references, in the context of above posts, and the Dependency Inversion Principle (DIP).
When we reference another assembly we take a dependency on it. If assembly A references assembly B it means that A depends on B. Taking this to the data access example, it means that a business logic assembly depends on the data access assembly.
This seems to be in contradiction with DIP which says that
High level modules should not depend on low level modules
The business logic is the high level module, and the data access is just details on how we get and store data. The contradiction may be even more clear if we refer to the The Clean Architecture of Uncle Bob, where he points that the application should not depend on frameworks.
Let’s look more closely into DIP, and let’s focus on the INVERSION word. DIP doesn’t only say that we invert the dependency, but more importantly we do it by inverting the ownership of the contract (the interface).
After DIP is applied as above diagram shows, the contract is owned by the high level layer, and no longer by the low layer. The essence in DIP is that the changes on the contract should be driven by the high level modules, not by the low level ones. When the contract ownership is inverted, the dependency is also inverted, because now the low level module depends on the high level one by complying with its contract.
In our example, the business logic assemblies depend on the
DataAccess assembly because the
IUnitOfWork interfaces are placed into the
DataAccess. If we would move them into the business logic assemblies, then we would invert the reference. Even more, now we could have more
DataAccess assemblies which have different implementations, one with Entity Framework one with NHibernate and at the application startup we could choose which one to use for that specific deployment or configuration.
However, this is not practical. We may have more business logic assemblies that need to access data, so which one should contain these references? We could make an assembly only with the data access interfaces, to solve it. With this, we would also keep the possibility to have more data access implementations. But do we really need more data access implementations? In most cases we don’t. So, it doesn’t worth to separate them.
Now, coming back to the initial question: if we keep the data access interfaces into the
DataAccess assembly and the rest of the assemblies reference it, are we following DIP?
YES, as long as these interfaces change ONLY based on the needs of the business logic modules and not because of implementation details, we follow DIP. From a logical separation point of view they are owned by the business logic layer, and the data access implementation depends on them. For practical reasons they are placed in the same assembly with the implementation, because it doesn’t worth creating one only with the interfaces for this case.
As long as the implementation details and specifics do not leak into these interfaces, they represent correct abstractions and the implementation remains well encapsulated.
Following the same reasoning, I some times create a Contracts assembly, which contains the underlying abstractions of the applications. These abstract concepts that are specific to the application and not to one module. They are the truths that do not vary when the details are changed. I may have more functional modules, which have assemblies that implement or consume these contracts.
This figure shows this, by outlining that the functional modules do not reference one on each other but they all reference the
Contracts assembly. If you go deep into DIP description in Uncle’s Bob paper here, you will find this approach very similar with the Button-Lamp example from Finding the Underlying Abstraction section.