oopsolid-principles

Does the Dependency Inversion Principle apply within application layers


I keep reading code examples that suggest that the Dependency Inversion Principle is achieved by using interfaces for a classes dependencies. That in itself is confusing me, as the more I read about DIP, the more it appears to be an architectural solution - applied in specific areas (at layer boundaries) within an application - rather than a pattern that should be applied to all classes everywhere, all the time.

Taking the concept of abstraction layers (DDD, Hexagonal or whatever), using an application layer and a transportation layer, it makes sense that we use an interface at this boundary, since then the transportation method (HTTP, Kafka, DB) can change, without impacting the layer which consumes it.

But I am more confused if DIP applies to relationships between classes within the same layer. For example, we have a complex class / logic in the domain layer. We split it up to keep it maintainable and follow Single Responsibility Principle, but it is still complex. It has dependencies from both the application layer and domain layer. For its application layer dependencies, we use interfaces since those dependencies cross the application / domain layer boundary (and the application layer could change over time), but what about the dependencies within the domain layer?

To expand further, the dependencies within the domain layer are logic that I instinctively know will not change in the applications lifecycle. The dependencies are, from a logic point of view, tightly coupled to each other. (The same argument could also apply to complex logic / classes in any other layer)

If we take a traditional interface (in languages which support it) at face-value, it defines a contract to be implemented. This is beneficial at boundary layers for exactly the reason that the layer can change without impacting whatever is consuming the interface. But in the example where we know the logic will not change, within the same layer, is there any benefit to using an interface? - And is that even DIP?

In a previous (poorly worded question), and from reading online, one benefit I had read to using interfaces for - all - dependencies - everywhere -, was that it made testing easier in some frameworks, but it appears that is simply not true.

So to highlight my two main points of confusion; Does DIP apply to


Solution

  • On a possible source of confusion

    I keep reading code examples that suggest that the Dependency Inversion Principle is achieved by using interfaces for a classes dependencies.

    That's unfortunate, and I can see how this is confusing, but the more nebulous a term, the more vulnerable it is to semantic diffusion. As non-mathematical notions go, the Dependency Inversion Principle (DIP) isn't even that vague, so the core idea remains relatively intact a few decades after the idea was first presented.

    Even so, the more people rehash an idea and put their own spin on it, the more diluted it becomes, and the more mistakes creep in.

    One thing that bothers me about the software industry broadly speaking is the general lack of academic rigour. If authors of blog posts etc. even cite any sources at all, it's often some other derived piece of work.

    All this to say that you may find it illuminating to go back to the original source. The five SOLID principles originate from Bertrand Meyer, Barbara Liskov, and Robert C. Martin, but were popularized as a 'package' by Martin (with the help of Michael Feathers, who suggested the acronym).

    When things are confusing, it sometimes help cutting through all the intermediate layers of interpretation and instead consulting the source. As a start, I'll recommend going back to Robert C. Martin's work, which is still quite readable. You may be able to find scattered PDFs from the nineties, when he first started to explore the principles, but otherwise, one of the APPP books should help (I usually refer to the C# version, but there's an earlier edition with examples in Java, I believe).

    I'm sorry about this long-winded preamble, but I understand the confusion. If you're consulting 'random' articles on the internet, that can happen.

    And to be blunt, although I did link to Wikipedia, I consider its articles on SOLID etc. to be of varying quality. And you never know what claims a given Wikipedia article will make tomorrow.

    If all of this establishes that there's a lot of random people on the internet who write down their idiosyncratic interpretations of things, what makes me different? Why should you listen to me?

    I'm not that different, and you should only listen to me if you can make sense of what I write. Perhaps I've already lost you.

    I do, however, attempt to cite original sources when possible. (I also link a lot to my own blog, which isn't an attempt at sleight of hand, but mostly to link to a more expanded argument than what fits here on Stack Overflow.)

    Architecture

    the more I read about DIP, the more it appears to be an architectural solution

    That's also how I interpret Robert C. Martin's writings on the topic.

    It has dependencies from both the application layer and domain layer

    We have to be careful here. As I read the OP, from the context I take it that you understand the concept correctly. Even so, precision in language may at times prevent confusion.

    You're here talking about run-time dependencies; i.e. when running, an object depends on another object that I may invoke in order to get work done.

    This isn't the same as a compile-time dependency (in compiled languages), where, in order to compile a piece of code, another library or module also has to be available.

    A major reason why the DIP uses the term inversion is because it inverts the direction of dependencies so that run-time dependencies and compile-time dependencies point in different directions. At run time the dependency points one way, but at compile time it points the opposite way.

    Inter-layer dependencies

    Does the DIP apply within a given layer?

    I usually don't interpret it that way, but I suppose that it ultimately depends on how big and complex a given 'layer' is.

    Notice that the DIP doesn't mention interfaces, but rather talks in terms of abstraction:

    "A. High-level modules should not depend on low-level modules. Both should depend on abstractions.

    "B. Abstractions should not depend upon details. Details should depend upon abstractions."

    - APPP, Robert C. Martin, Prentice Hall, 2006

    In APPP Robert C. Martin only obliquely refers to a definition of 'abstraction', but as far as I've been able to track down, we can find it in one of his earlier works:

    Abstraction is the elimination of the irrelevant and the amplification of the essential.

    - Designing Object-Oriented C++ Applications Using The Booch Method, Robert C. Martin, Prentice Hall, 1995, chapter 00, his emphasis

    My point is that the DIP does not constrain itself to discuss interfaces or other kinds of polymorphism. Rather, it works on the level of a 'hierarchy of abstraction'.

    You could, conceivably, have such a hierarchy even within a module, but I don't think that it's the most common scenario.

    Relatedly, however, I strongly recommend that you explicitly consider dependencies even within modules. Scott Wlaschin has a fascinating analysis of dependency cycles in real-world code, on which Dr. Evalina Gabasova later expanded. The more cyclic dependencies you have, the closer you get to spaghetti code.