Categories
Software Architecture Software Development

Dependency Inversion applied for real

The Dependency Inversion Principle helps us building decoupled systems, and one way to achieve this is by turning some classes into interfaces and using Dependency Injection, Service Locator, Factory or some other pattern, right?

Well then. I believe you already know or have at least heard about these concepts and techniques, but even then there’s a chance you’re misapplying the principle. If you’re applying it and are still getting a coupled system, then this article is for you.

A little dare

I dare to say that many developers, even some more experienced ones, still haven’t figured out what this principle really means.

First, because Dependency Inversion is not the same as Dependency Injection. Second, although Dependency Injection is one of the technical ways to obtain inversion, it is still possible (and actually very easy) to apply Dependency Injection and still get a coupled software, that is, with the dependencies not inverted.

Wanna see it?

Inverted dependencies… or are they?

Have a look at this code and think for a minute: how many times have you come across generic interfaces like this in the systems you worked on?

public interface IGenericRepository<T>
{
    void Insert(T entity);
    void Update(T entity);
    void Delete(T entity);
    T Select(object key);
    IQueryable<T> EvilSelect();
}

Considering a system that supposedly applies Dependency Inversion but injects this interface into various business services, my question is: do you really think this type of interface to be valid?

If your answer is yes, you have not yet understood dependency inversion, and as much as you are using the Dependency Injection technique, your dependencies are still in the wrong direction.

With this approach, your business rules are 100% dependent on the database. Reasons being:

  • Not every entity will be deleted. The shopping cart for one, in most e-commerce web sites, will only be marked as completed or abandoned, and will remain there for business managers to do analytics and reporting, and may also serve as a basis for marketing campaigns and other business actions.
  • Not every entity will be updated. In some contexts, entities are just inserted, and are never updated or deleted. This is the case with some transaction records, for example in banking.
  • Nem toda entidade será lida individualmente. Alguns tipos de dados são inseridos apenas para serem agregados mais tarde e consultados em grupo, como é o caso de contadores em geral (page views, cliques, outros).
  • Not every entity will be read individually. Some types of data are inserted only to be aggregated later, as is the case of counters in general (page views, clicks, others).

Such an interface does not model persistence methods according to business needs. Worse yet, it makes it possible for the new programmer in the team to invoke any of the methods he shouldn’t, introducing bugs into the system.

But then you ask me: “Hey Phill, I’m developing software: is it not OK for the business to depend on my database or my APIs?” Nope 🙂 Never.

Direct dependencies

Thinking of the generic repository interface above, more than a technical dependency between classes, there is a technical dependency between the business rules and the database.

It is important not to think as a programmer for a moment to understand this: does it make sense for a shopping cart to depend on a database? No!

In most cases, it makes no sense that any business rules depend on a database, as the business rules represent an objective that a company or organization wants to achieve, and the database is a technical detail needed only when you’re applying software to tackle the problem.

You just need to imagine how this process would happen without software: the customer enters the market, puts some products in the cart, pays and leaves. The word “database” doesn’t even appear.

Obviously, equivalences of the type “product registration”, “catalog” or “book” would appear, but these are generic concepts, used only to point out that the information is “remembered” by someone or stays somewhere (anywhere), and that its shape does not matter (or does very little) to the business process itself.

On a day without power, the market could still be open for business. Perhaps accepting only cash payments, or taking notes in a passbook for loyal customers … The point here is that the market does not exist because of technology; it is technology that has evolved to improve processes that have worked before.

Então, a não ser que o software que você está desenvolvendo seja algo como uma ferramenta para gestão de banco de dados, o banco de dados é algo que deveria ser deixado de lado enquanto pensamos nos assuntos de negócio que estamos resolvendo. Esses conceitos pertencem a planetas diferentes:

So, unless the software you are developing is something like a database management tool, the database is something that should be left aside while we think about the business problems the software is solving. These concepts belong to different planets:

  • Planet 1: people, shopping cart, car, animals, companies, businesses
  • Planet 2: database, API, systems forms

It is important to note that, although I picked the database as my scapegoat, the same goes for any concept that is infrastructure, or that is not business for that matter. The database is the most common, but you can also think of APIs, import and export systems, email sending, etc.

Thinking differently

Ao escrever um software, o foco da implementação deve estar no problema de negócio que está sendo abordado, não nos detalhes que só pertencem ao software e que não apareceriam no negócio se o problema estivesse sendo abordado sem envolver a programação.

When writing software, your focus should be on the business problem being addressed, not on the details that belong only to the software and that would not appear if the problem was being addressed without involving programming.

The details need to exist, but they have their place, and this place is far from the domain objects.

Let’s review the definition of the principle. It reads:

High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).

High-level modules are domain entities and business rule classes. Low-level modules are the infrastructure components: database connection, API access, reading files, among others.

Before inverting the dependencies, let us review another analogy:

A company without customers doesn’t exist (or it doesn’t make sense that it exists), right? Instead of the customer depending on the company, it is the company that depends on the customer. Therefore, the company models the services it provides according to a market need that it has identified.

Dependency Inversion is like thinking that the database does not need to exist either if there is no business class that it needs to serve. The methods that this database class exposes are defined according to the needs of the business class.

Applying the Dependency Inversion

Instead of making the shopping cart depend on the database, let’s make the database depend on the shopping cart 😉

This means that, when modeling the classes and layers of this e-commerce, you will not start with the database interfaces and the CRUD methods that they will expose. You’ll start with the business classes (product, cart, order, buyer), always thinking about what business operations they model, and not just what data they store, as that would be putting the database back in the first place.

You are a good person, so you will start by writing unit tests that take the names of the business use cases. Then you’ll create domain entities, combining data and behavior (fields and methods), so that you don’t have anemic entities. When there are rules that cannot be met within a domain entity, then you’ll start to design domain services.

Eventualmente você vai chegar na parte de falar de banco de dados. Mas, até lá, você já tem boa parte do seu sistema “funcionando” com base em testes unitários, entidades, serviços e eventos, sem qualquer menção a qualquer conceito de infraestrutura. A partir daí é que as necessidades específicas de infraestrutura começam a aparecer, e você vai definir as interfaces especificamente de acordo com cada necessidade.

Eventually it will come the time to talk databases. But, by then, you’ll already have the best part of your system “working” based on unit tests, entities, services and events, without any mentions of any infrastructure concepts. Only then, specific infrastructure needs will start to appear, and you will define interfaces according to those specific needs.

Abstractions should not depend on details. Details should depend upon abstractions.

Conclusion

The real Dependency Inversion rethinks several concepts that we see every day in the code bases out there.

This principle is one of those that, when really understood and applied, helps you to develop software that solves business problems, instead of adapting business to the way programmers think.

Good programmers understand the business and question the efficiency of the processes they receive detailed in documents, “ready” to be translated into code.

In doing so, they begin to positively transform the business, influencing the remodeling of processes, adding more value at the end.

And only then do they turn it into code, but then it is code that other people can read, understand and maintain more easily.

By Phillippe Santana

Passionate about writting code that people can understand, I'm a software developer, a project manager, an entrepreneur, and people/culture enthusiast. Find me on [Linkedin](https://www.linkedin.com/in/phillippesantana/) and on [Medium](https://medium.com/@phillippesantana).