This article is a revised version. It was previously published at refactoring-legacy-code.net published.
Dependencies cause trouble
Dependencies are the fundamental evil of software development. Many developers take them for granted. After all, there is the Dependency Inversion Principle (DIP). And there are numerous Dependency Injection Frameworks such as Unity, Spring, Castle Windsor, StructureMap and what they are all called. These frameworks are dedicated to the topic of dependencies and reduce the effort that developers have to put into automated testing. In my observation, however, the careless handling of dependencies leads to several problems that even the DIP does not completely solve. This is what the following section is about and, of course, how dependencies can be reduced. Let's first look at some of the reasons why dependencies make software development more difficult.
Dependencies make it difficult to understand the software
If one functional unit is dependent on another, it becomes more difficult to understand. Functional units that have no dependencies do their thing, done. They are easier to understand because the functionality is completely visible. No partial functionality is transferred to another functional unit, but the task is solved completely on its own. Of course, understanding is only easy if the functional unit has a clear responsibility (Single Responsibility Principle (SRP)) and is manageably large. However, this applies equally to functional units with dependencies.
Dependencies make automated testing more difficult
As soon as dependencies are involved, the tests become more complex. Of course, I can always write integration tests that test a class including its dependencies. In the end, I should definitely even write some integration tests to ensure that the individual parts involved work together correctly. But integration tests should always be outnumbered by unit tests. And testing a class with dependencies in isolation means that interfaces and dummies are needed. And lo and behold, frameworks come to the rescue again: this time mock frameworks such as Rhino Mocks, JustMock, Moq, NSubstitute, FakeItEasy, etc. But here too, instead of alleviating symptoms, it is better to cure the disease.
Dependencies make changes difficult
If a functional unit on which others are dependent is to be changed, care must be taken to ensure that the dependents are adjusted if necessary. With a 1:1 relationship, this is still manageable. However, if several dependencies are involved, the change can quickly become complex. If, for example, a method from the domain logic area repeatedly calls methods from the persistence area, a change is rather difficult.
An example
It is clear that the three aspects of user interface (Ui), domain logic (Domain) and database access (DB) must be separated. This follows from the Single Responsibility Principle (SRP). But how do we get the classes involved to work together in a suitable way? The following dependency structure is usually chosen:
The user interface is dependent on the domain logic. It calls this. Of course, an interface is placed in between for the domain logic. The Ui is therefore dependent on the interface and not directly on the domain logic. The same applies to domain logic and database access. The domain logic is dependent on database access. Here too: not directly, but via an interface. This approach results from the Dependency Inversion Principle (DIP). But does this already solve all problems optimally?
I mean no, because tests of the domain logic are now relatively complex, as dummies have to be used for the database accesses. You can't just call the domain logic as it communicates with the database. So a dummy is passed in during testing, then the domain logic communicates with the dummy instead of the real database. Of course, this improves things quite a bit, because now I can test the domain logic automatically in isolation without having to set up a database every time.
Reduce dependencies
The controller then supplies the data to the Interactor. This integrates the domain logic with the resource accesses. This means that the domain no longer has a dependency. Instead, the domain logic does its thing and returns data to the interactor if necessary. The interactor then delivers this data to the database so that it can be persisted there. This results in the same data flow at runtime as at the beginning: data flows from the Ui via the domain logic to the database. Of course, the data does not flow in exactly the same way, but takes a "detour" via the controller and the interactor.
Testability
The biggest advantage here is for the testability of the domain logic: it is no longer dependent on the database accesses. In the first example, the domain logic was still a void-method with a side effect. Now it is a function.
In the test, the domain logic is called with input data from which it produces output data. Such code is easy to test automatically. No dummies are required. The interface is also no longer required and dependency injection can be dispensed with.
Readability
As the domain logic is no longer dependent on the database, it is easier to understand. It receives input data and produces output data from it. The responsibility for addressing the database in a suitable manner has been transferred to the interactor. In order to understand the domain logic, it is no longer necessary to understand how the database logic behaves, as the domain logic is no longer dependent on it.
Web applications
In the web environment, a variant of the model described above is suitable. In web applications, it is common for the top-level classes to be instantiated by the infrastructure. URLs are routed to classes. These are then instantiated by the infrastructure, be it ASP.NET, Spring or similar. In such a model, it is very helpful if the dependencies are defined via Dependency Injection can be resolved. Finally, the infrastructure then ensures that all required objects are instantiated. The following dependency structure is suitable for such cases.
Here, the Ui is instantiated by the framework. The interactor can be injected from the infrastructure into the Ui. The required dependencies to the domain and the database are also injected, as was the case in the initial example. However, as the interactor only represents a thin integration layer, this class does not need to be tested in isolation.
Therefore, the use of dummies in the test can also be dispensed with for this model. Please note that integration tests are still required for the interactor. Domain logic and database are again leaves in the dependency tree. This means that these parts can be easily tested in isolation without having to use mockups.
Conclusion
As soon as you start to consciously deal with dependencies, to plan for them, they lose their horror. Ultimately, it comes down to consistently IOSP to apply. This enables us to reduce dependencies. IOSP stands for Integration Operation Segregation Principle. It states that integration and operation must be separated. The task of Integration is to deal with dependencies. Dependencies accumulate at a few points here.
On the other hand, integration does not contain any domain logic. This is the responsibility of the Operations. These are exclusively responsible for logic and, conversely, are not burdened with dependencies. On a small scale, the IOSP provides methods that are easy to understand and have clear responsibilities. Furthermore, the Single Level of Abstraction (SLA) principle is adhered to. On a large scale, the IOSP leads to structures in which dependencies are isolated and released at the level of classes, libraries, packages and components.
In most applications, the music plays in the domain logic. This should therefore be easy to understand and easy to test. As soon as the domain logic is freed from dependencies, this is much easier.
The right training
The procedure described here is used in our training Clean Code Developer Basics treated. You will learn what challenges the Dependency Inversion Principle (DIP) and how this is linked to the Integration Operation Segregation Principle (IOSP) can be solved. Please contact Contact us up with us!