From the Dependency Inversion Principle (DIP) to the Integration Operation Segregation Principle (IOSP)
ย
For many developers, the Dependency Inversion Principle (DIP) is one of the most important principles. After all, it is part of the SOLID principles, so it must be important. But is the DIP really that important? Or are there alternatives that lead to even better results? The following article aims to answer these and similar questions.
Before we turn to the question of whether the dependency inversion principle can be supplemented or even partially replaced by another principle, we need to clarify how the DIP came about in the first place. The subject of the DIP is dependencies. The following illustration shows a dependency between two functional units.
Dependency between two functional units
ย
On an initially purely technical level, we consider the direction of this dependency to be the wrong way round. This is why the dependency arrow is shown in red. The wrong way round here means that the direction should be from bottom to top rather than from top to bottom.
It could mean, for example, that the direction should not run from the domain logic to the database, but the other way around. Initially, it does not matter for what reason the direction should be reversed. The dependency inversion principle suggests a solution as to how the direction can be reversed, from a purely technical point of view. See Figure 2.
Contract between A and B
ย
Here, a contract for B, called BC, is inserted between A and B. A is no longer directly dependent on B, but is instead dependent on BC, the contract for B. Since B implements the contract, B is also dependent on BC. And this results in the reversal of the direction of dependency.
It now runs from bottom to top instead of from top to bottom as in Figure 1. Now one could argue that it only requires a different representation to have a dependency from top to bottom again. Figure 3 shows this consideration.
Vertical arrangement of the functional units
ย
Now we again see a dependency from top to bottom, i.e. again in the unintended, wrong direction. In contrast to Figure 1, however, the concrete A is now not dependent on something concrete, but on the abstract contract. In this respect, the dependency from A to BC is weaker than the original dependency from A to B.
This form of direction reversal, through indirection via a contract, is known as dependency inversion and is the core statement of the dependency inversion principle (DIP).
Advantages of the dependency inversion principle
ย
Let's take a look at the advantages this brings. Because A is no longer dependent on the concrete B, but on BC, the contract for B, we are able to fulfill the dependency in different ways. On the one hand, the contract can be provided in automated tests by a dummy, on the other hand, different variants of the implementation of the contract can be provided.
Testing with dummies
ย
If we use a dummy instead of the "real" B in automated testing, we are able to test the concrete A in isolation. Without the dummy option, see Figure 1, only the integration test of A incl. B remains. From a technical point of view, we also need the concept of dependency injection (DI) for testing with dummies. This enables us to decide at runtime which specific implementation of the contract BC should be used by the functional unit A. Without DIP and without DI, we are not able to influence the structural relationship between A and B at runtime. A uses B directly. Using the DIP in conjunction with DI, we can decide at runtime whether to provide A with a B or any other functional unit that adheres to the contract BC. From A's point of view, this makes no difference, as A does not expect a B but a BC. In the automated test, we can therefore pass a dummy of B to A. This enables us to test A without the real B being involved.
ย
Interchangeability of the implementation
ย
The second benefit of dependency inversion is that different variants of an implementation of BC can now be used. In this way, for example, we are able to realize the storage of data once with an SQL server and the other time with a CSV file. As long as both implementations satisfy the BC contract, they can be injected into the user without the user being able to tell the difference. This second advantage that arises from DIP and DI is actually one that is not called into question by the IOSP below. However, such an exchange of an implementation rarely takes place in practice. In this respect, it makes little sense to always work with interfaces across the board just to provide for interchangeability, which is then hardly ever used in practice. Let us now take a look at whether there is an alternative to the dependency inversion principle.
ย
The aspect of integration
ย
The starting point of the consideration is the fact that two aspects are mixed in A. On the one hand, A is responsible for fulfilling its obvious task: A contains logic. As a rule, this task can be recognized from the name. It is obvious that A must contain logic. A functional unit A that only calls a method from B and otherwise contains no logic of its own makes no sense. Due to the dependency on B, however, A has another responsibility: the integration of B. A uses B, otherwise A would not be dependent on B. A obviously does something with B. Technically speaking, A calls a method from B, for example. This call means that an integration is taking place here. This is different with B. B has no dependencies, does not call anyone and therefore does not integrate any other functional unit. We call this activity an operation. And now comes the crucial point: In A, the two aspects of integration and operation are mixed.
Method calls
ย
In order to distinguish between the two categories of integration and operation, we need to differentiate which other methods a method calls. If a method only calls its own methods, i.e. methods that also belong to the solution, we speak of integration. "Belonging to the solution" here means that the methods belong to the same software system or are processed by the same developers as the integrating method. The situation is different when methods are called from frameworks or runtime. Methods that call framework or runtime methods are referred to as operations. Furthermore, expressions such as "x > 42" or "y + 1" are also used in operations. This results in a very simple rule for differentiation:
In a Integration the following is permitted:
- Calling other methods that are also part of the solution
In a Operation the following is permitted:
- Calling methods from frameworks or the runtime
- Expressions
These criteria are exclusive. This means that we can use these rules to easily recognize whether a method is responsible for the aspect of integration or operation. Furthermore, it is also easy to recognize whether both aspects are mixed in a method.
The Integration Operation Segregation Principle (IOSP)
ย
In Figure 1, the two aspects of integration and operation are mixed up. This is recognizable because A obviously uses B. This results from the dependency. Furthermore, it is obvious that A is also responsible for the operation aspect. If A did not contain an operation part, the dependency from A to B would make little sense, since A depends exclusively on B and is therefore not responsible for serving various dependencies. What conclusions can we draw from this? We can also solve the problem that is solved in Figure 1 by a dependency from A to B in another way. Figure 4 shows how this can be done.
Integration as an independent aspect
ย
Another functional unit has now been added, whose task is to integrate A and B. A and B are each operations. This means a significant change for A: A is now freed from the aspect of integration. And this brings us to the crucial point: the direction of dependency no longer needs to be reversed.
Injection is also no longer necessary. This is because functional unit I is exclusively responsible for integration and A and B are exempt from dependencies. This means that both can be tested automatically without having to work with dummies.
Conclusion
ย
If we consistently distinguish between integration and operation, we reduce the need for DIP and DI to a minimum. The DIP is then no longer required for testability. This leaves only the task of using DIP and DI to support alternative implementations. However, as already described, this is rarely necessary. We can therefore conclude that DIP and DI can be completely replaced by the IOSP for testability. Only the rare use for alternative implementations remains.
Both the Dependency Inversion Principle (DIP) as well as the Integration Operation Principle (IOSP) are among the principles of the Clean Code Developer Initiative.
If you need detailed information on how to use DIP and IOSP or want to learn how a codebase can benefit from them, book one of our Clean Code seminars.
4 thoughts on “DIP oder IOSP”
Thank you for this article. We are working here on a product using DIP with DI and are also unconsciously following the IOSP as far as possible, simply because of the clarity and simpler testability.
What I don't quite understand is why DI can be largely dispensed with in IOSP.
If code should be so flexible that most operations should be interchangeable for each application, then in my opinion this is only possible via interfaces.
Hello and thank you very much for asking.
DIP/DI is indeed the solution for alternative implementations. However, the question is how often this actually occurs. According to my observations, quite rarely. I think you should only use interfaces where you are actually working with more than one implementation. Not in stock, but only when there is a specific need.
The main thing is that you can manage without dummies when testing.
Thank you for answering my question so promptly.
As one of the biggest USPs of our product compared to the competition is its adaptability to customer requirements, this affects a large proportion of operational functions.
Nevertheless, I think IOSP's approach is very good. We will "consciously" pursue this in future ๐