Practical SOLID in Golang: Dependency Inversion Principle
·2306 words·11 mins·
loading
·
loading
Learning a new programming language is often a straightforward process. I often hear: “The first programming language you learn in a year. The second one in a month. The third one in a week, and then each next one in a day.” Saying that is an exaggeration, but it is not too distant from the truth in some cases. For example, jumping to a language relatively similar to the previous one, like Java and C#, can be a straightforward process. But sometimes, switching is tricky, even when we switch from one Object-Oriented language to another. Many features influence such transitions, like strong or weak types, if a language has interfaces, abstract classes, or classes at all. Some of those difficulties we experience immediately after switching, and we adopt a new approach. But some issues we experience later, during unit testing, for example. And then, we learn why The Dependency Inversion Principle is essential, especially in Go.
When we do not respect The Dependency Inversion # High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Above is the definition of DIP as presented by Uncle Bob in his paper. There are also more details inside his blog. So, how can we understand this, especially in the context of Go? First, we should accept Abstraction as an object-oriented programming concept. We use this concept to expose essential behaviors and hide the details of their implementation.
Second, what are high and low-level modules? In the context of Go, high-level modules are software components used at the top of the application, such as code used for presentation. It can also be code close to the top level, like code for business logic or some use-case components. It is essential to understand it as a layer that provides real business value to our application. On the other hand, low-level software components are mostly small code pieces that support the higher level. They hide technical details about different infrastructural integrations. For example, this could be a struct that contains the logic for retrieving data from the database, sending an SQS message, fetching a value from Redis, or sending an HTTP request to an external API. So, what does it look like when we break The Dependency Inversion Principle, and our high-level component depends on one low-level component?