Dependency inversion in architectural patterns #
I am no fan of SOLID – to the extent of being unable to remember what those five letters mean – thus I was really surprised to notice that one of its principles, namely dependency inversion, is quite common with architectural patterns, which means that it is way more generic than OOP it is promoted for.
Let’s see how dependency inversion is used on system level.
Patterns that build around it #
Both Plugins and the derived Hexagonal Architecture rely on dependency inversion for the same reason – to protect the core, which contains the bulk of the code, from variability in the external components it uses. The core operates interfaces (SPIs) which it defines so that it may not care whatever is behind an interface.
It is the nature of the polymorphic components that distinguishes the patterns:
- Plugins allow for small pieces of code, typically contributed by outside developers, to provide customizable parts of the system’s algorithms and decision making. Oftentimes the core team has no idea of how many diverse plugins will be written for their product.
- Hexagonal Architecture is about breaking dependency of the core on external libraries or services by employing adapters. Each adapter depends both on the core’s SPI and on the API of the component which it adapts. As interfaces and contracts vary among vendors and even versions of software, which we want to be interchangeable, we need adapters to wrap the external components to make them look identical to our core. Besides, stub or mock adapters help develop and test the core in isolation.
Patterns that often rely on it #
A few more metapatterns tend to use this approach to earn its benefits, even though dependency inversion is not among their integral features:
- Microkernel, yet another metapattern derived from Plugins, distributes resources of providers among consumers. Polymorphism is crucial for some of its variants, including Operating System, but may rarely benefit others, such as Software Framework.
- Top-Down Hierarchy distributes responsibility over a tree of components. If the nodes of the tree are polymorphic, they are easier to operate, and there is dependency inversion. However, in practice, a parent node may often be strongly coupled to the types of its children and access them directly.
- In another kind of Hierarchy, namely Cell-Based Architecture (aka Services of Services), each Cell may employ a Cell Gateway and outbound Adapters to isolate its business logic from the environment – just like Hexagonal Architecture does for its monolithic core.
Patterns that may use it #
Finally, two basic architectures, Layers and Services, may resort to something similar to dependency inversion to decouple their constituents:
- We often see a higher layer to depend on and a lower layer to implement a standardized interface, like POSIX or SQL, to achieve interoperability with other implementations (which is yet another wording for polymorphism).
- A service may follow the concept of Hexagonal Architecture by using an Anti-Corruption Layer [DDD] or CQRS Views [MP] as Adapters that protect it from changes in other system components.
Summary #
Many architectural patterns employ dependency inversion by adding:
- an interface to enable polymorphism of their lower-level components or
- Adapters to protect a component from changes in its dependencies.
The two approaches apply in different circumstances:
- If you can enforce your rules of the game on the suppliers of the external components, you merely define an SPI, and expect the suppliers to implement and obey it.
- If the suppliers are independent and it is your side that adapts to their rules, you should add Adapters to translate between your lovely SPI and their whimsical APIs.