D do SOLID - Inversão de dependências no Laravel
Tags: php, design, programming, laravel
Published: 2023-03-07 17:33:26


![capa da publicacao](https://cdn-images-1.medium.com/max/2800/1*n-mRpCbekpAIhaeizu7XCA.png)

## D do SOLID — Inversão de dependências no Laravel

Bom, você deve estar se perguntando por que a **inversão de dependências** é o **D** do [SOLID](https://en.wikipedia.org/wiki/SOLID) e não o **I**. Pra começar, quase tudo em programação é derivado do inglês assim como essa definição que vem de **Inversion Principle**. Um conceito que foi definido por [Robert C. Martin](https://pt.wikipedia.org/wiki/Robert_Cecil_Martin) que diz:

 1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

 2. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

A teoria sempre parece complicada, principalmente quando não entendemos todos os termos da definição. O ponto chave aqui é entender o que são módulos de alto e baixo nível, mas, também, o conceito de abstração. Vamos usar duas classes abaixo pra ilustrar esses conceitos:

* UserRepository.php

{% gist https://gist.github.com/caioflavio/51525a646c937c92f9e2a07177ff53c7.js %}

* UserService.php

{% gist https://gist.github.com/caioflavio/894e8ccf8ab9e6c396b8fbf1298255d7.js %}

No caso acima, ambas as classes são de alto nível, pois a **UserService** depende da classe **UserRepository** que, por sua vez, depende da classe **Module\Database\DBConnection** que é a nossa classe de baixo nível. Então, temos definido que:

**Classe de alto nível**: uma classe que possui dependências de outras classes pra ser executada.

**Classe de baixo nível**: uma classe que não possui dependências de outras classes.

Normalmente, utilizamos as classes de alto nível para descrever a nossa regra de negócio, e as classes de baixo nível para resolver detalhes da infraestrutura, como, por exemplo, a implementação da conexão com o banco de dados e/ou com o servidor de e-mail.

Nesse caso, estamos usando alguns [<u>padrões de projeto</u>](https://pt.wikipedia.org/wiki/Padr%C3%A3o_de_projeto_de_software) como o [<u>Service Pattern</u>](https://en.wikipedia.org/wiki/Service_layer_pattern) e o [<u>Repository Pattern</u>](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design) que nos ajudam a separar as responsabilidades da nossa aplicação, onde a **service** tem a responsabilidade de encapsular parte da nossa regra de negócio e a **repository** tem a responsabilidade de persistir os dados de um contexto específico. 

Não iremos entrar em detalhes sobre esses padrões de projeto porque não é o objetivo desse artigo, mas os princípios de [SOLID](https://pt.wikipedia.org/wiki/SOLID#:~:text=No%C3%A7%C3%B5es&text=%22uma%20classe%20deve%20ter%20apenas,a%20especifica%C3%A7%C3%A3o%20da%20classe) são sempre utilizados em conjunto e é quase impossível falar do conceito de inversão de dependência sem utilizar os outros princípios, principalmente o [princípio da responsabilidade única](https://en.wikipedia.org/wiki/Single-responsibility_principle).

Então, de maneira bem resumida, quando falamos que as classes de alto nível **não devem** ser dependentes das classes de baixo nível estamos dizendo que a nossa regra de negocio **não deve** ser dependente de detalhes de infraestrutura.

Tá, mas e aí? O que são as abstrações? E qual problema há em depender de uma classe de alto nível?

Abstração é um conceito importante da [Programação Orientada a Objetos](https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_objetos) e nós costumamos utilizar esse conceito quando pegamos um problema do mundo real, extraímos o seu contexto para nossas classes e descrevemos esse problema através de métodos e atributos que compõe a nossa classe.
Ou seja, nós criamos uma relação direta entre o comportamento do mundo real com a modelagem da nossa classe de modo que essa classe consiga lidar com o máximo de casos possíveis sem depender dos detalhes do problema. Para ilustrar isso, vamos criar a classe que realiza uma conexão com o banco de dados utilizando a extensão [PDO](https://www.php.net/manual/pt_BR/intro.pdo.php) nativa do PHP:

* App/Modules/Database/DBConnection.php

{% gist https://gist.github.com/caioflavio/2f64c7d18377d2113b473d14aacf4295.js %}

Bom, nossa classe cumpre o requisito de conectar com o banco de dados, mas aqui temos o primeiro problema, nossa classe depende de muitos detalhes como: tipo de banco, nome de usuário, senha e o *host*. 
E se o nosso sistema precisa se conectar em dois bancos diferentes? Pra evitar duplicarmos o código podemos **abstrair** esses dados pra que possamos reaproveitar nossa classe sempre que precisarmos conectar com um banco de dados sem ficarmos dependentes dos detalhes da implementação.

* App/Modules/Database/DBConnection.php

{% gist https://gist.github.com/caioflavio/01528bb055edc67c62b78a36562d080f.js %}

Pronto, agora nossa classe não depende mais de detalhes de implementação, pois eles foram abstraídos em atributos da nossa classe. Bom, mas e quanto a depender de classes de alto nível?

O exemplo acima estamos fazendo uma implementação bem básica de conexão com o banco de dados, ainda seria necessário implementarmos muita coisa pra ter um módulo realmente funcional, como a consulta, a inserção, filtros etc.

No entanto, pra nossa sorte hoje existem muitas bibliotecas mantidas pela comunidade que já fazem a maior parte de abstração das operações com banco de dados. E, por isso, não precisamos ficar reinventando a roda, podemos apenas utilizar uma dessas bibliotecas pra facilitar e abstrair esse trabalho pra gente.

Quando trabalhamos com o [Laravel Framework](https://laravel.com), normalmente utilizamos uma biblioteca já inclusa chamada [Eloquent](https://laravel.com/docs/9.x/eloquent) para realizar as operações com banco de dados. O Eloquent é um [ORM](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) que r̶e̶s̶o̶l̶v̶e abstrai muitos desses problemas relacionados ao banco de dados pra gente.

A principal vantagem de utilizar um ORM é que eles criam uma camada que abstrai os detalhes da implementação dos banco de dados, ou seja, ele não é dependente dos detalhes da implementação do banco de dados. Em um mundo perfeito, onde seguimos todas as recomendações e boas práticas do componente de ORM, caso seja necessário mudar de um banco [MYSQL](https://www.mysql.com/) para um [PGSQL](https://www.postgresql.org/), por exemplo, não precisaríamos nos preocupar em reescrever nada, pois os métodos do ORM se encarregariam de transformar as ações que são realizadas em um formato que o novo banco de dados é capaz de entender. Tá vendo aí a importância de não depender de detalhes de implementação? Quando menos dependemos dos detalhes mais abrangente é nosso código.

Bom, então vamos trocar a nossa implementação de banco de dados simples pelo Eloquent e ver como as coisas ficam:

* App/Modules/User/Models/User.php

{% gist https://gist.github.com/caioflavio/a643e14d61ff45bdd3adf2e7698e8298.js %}

Utilizando poucas linhas de código temos uma classe que é responsável por representar a nossa tabela de usuários. Além disso podemos fazer inúmeras ações como [criar usuários](https://laravel.com/docs/9.x/eloquent#inserts), [atualizar usuários](https://laravel.com/docs/9.x/eloquent#updates), [consultas simples](https://laravel.com/docs/9.x/eloquent#retrieving-models) e [complexas](https://laravel.com/docs/9.x/eloquent#building-queries), mesmo que você não esteja familiarizado, é só seguir a [documentação](https://laravel.com/docs/9.x/eloquent) que é bem completa e repleta de exemplos.

Agora, vamos criar a [interface](https://pt.wikipedia.org/wiki/Interface_(programa%C3%A7%C3%A3o_orientada_a_objetos)) e a **classe** que será responsável pela persistência de dados, para inversão de dependência precisamos definir o [contrato](https://pt.wikipedia.org/wiki/Interface_(programa%C3%A7%C3%A3o_orientada_a_objetos)) da nossa classe, que será a abstração da qual vamos depender.

* App\Modules\User\Repositories\UserRepositoryInterface.php

{% gist https://gist.github.com/caioflavio/8566a815ace269898bb06d5a709f38fe.js %}

* Tests\Unit\App\Modules\User\Repositories\UserRepositoryTest.php

{% gist https://gist.github.com/caioflavio/6c7447fe632f7421760c2f9e59ffd118.js %}

A [interface](https://pt.wikipedia.org/wiki/Interface_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)) é a camada de abstração da qual nós vamos depender, lembra que no início falamos que devemos depender de abstrações e não de implementações? É exatamente isso que estamos fazendo, mas ainda não estamos prontos, se você tentar executar o teste nesse ponto provavelmente vai se deparar com o seguinte erro:

```
Target [App\Modules\User\Repositories\UserRepositoryInterface] is not instantiable while building [App\Modules\User\Services\UserService]
```

Por que não podemos instanciar as interfaces diretamente, eles funcionam apenas como um maneira de criar um "[contrato](https://pt.wikipedia.org/wiki/Interface_(programa%C3%A7%C3%A3o_orientada_a_objetos))", que diz como a nossa classe vai se comportar, e nesse contrato declaramos os métodos que ela possui e o retorno esperado de cada método. Por isso, agora iremos fazer a implementação desse nosso contrato.

* App\Modules\User\Repositories\UserRepositoryEloquent.php

{% gist https://gist.github.com/caioflavio/895c48028a95b176d6a1f64a25d4ea86.js %}

Agora que temos a nossa camada de persistência podemos criar nossa camada de serviço que será responsável pelas regras de negócio da nossa aplicação.

* App\Modules\User\Services\UserService.php

{% gist https://gist.github.com/caioflavio/a1521a817ffb6342e543136b168dfd9f.js %}

Agora que chegamos nesse ponto, ao rodar o teste novamente ainda nos depararemos com o mesmo erro, pois precisamos dizer ao Laravel qual a classe concreta da nossa interface, e ele nos permite fazer esse mapeamento por meio dos [Service Providers](https://laravel.com/docs/9.x/providers). Ou seja, ele se encarrega de criar uma referência entre a nossa camada de abstração e a implementação, para isso basta apenas adicionar uma linha no arquivo abaixo:

* App\Providers\AppServiceProvider.php

{% gist https://gist.github.com/caioflavio/57ee148646387b4a6555931ac07d4edc.js %}

Com o código adicionado na linha 18, o Laravel se encarrega de toda vez que instanciamos a nossa interface ele faz a referência entre a abstração e a implementação. Mas qual a vantagem desse trabalho todo?

Deixar nosso código flexível e bem definido. Vamos supor que as necessidades da sua aplicação tenham evoluído a um ponto que as ferramentas que o Eloquent oferece não são suficientes para resolver algum desafio encontrado, nosso código não é dependente do Eloquent e com poucas linhas de código seriamos capazes de trocar o ORM da nossa aplicação.

* App\Modules\User\Models\UserOtherORM.php

{% gist https://gist.github.com/caioflavio/e7ea56d714a87e7ed6bca50b39acf3a3.js %}

* App\Modules\User\Models\UserRepositoryORM.php

{% gist https://gist.github.com/caioflavio/79ea21ab61ed090588f57d6bfd596f68.js %}

Com as classes criadas, agora basta apenas alteramos a referências de service provider.

* App\Providers\AppServiceProvider.php

{% gist https://gist.github.com/caioflavio/b7ba8b551ba722881a2e8226d5eaf85a.js %}

Inclusive, nosso teste não depende da classe concreta, precisaríamos apenas executar os testes unitários, e, uma vez que eles nos digam que está tudo ok, podemos prosseguir com a substituição do ORM sem tanta preocupação em quebrar o nosso sistema.

Nesse nosso cenário temos apenas uma classe dependendo da nossa lógica de usuários, mas imagine em um sistema complexo onde quase tudo dependeria da classe de usuários, imagine ter que procurar arquivo por arquivo pra fazer essa alteração? Sem ter a certeza que realmente trocou onde deveria trocar? A inversão de dependência facilita muito a manutenção da nossa aplicação!

## Conclusão

Você pode estar pensando, mas quais as chances de eu precisar trocar meu ORM ou qualquer outra biblioteca que eu esteja utilizando? Pra ser sincero, em projetos pequenos, são bem baixas. Mas mesmo pra projetos pequenos se seu objetivo é criar um *software* de qualidade, de fácil entendimento, escalável e de fácil manutenção, a inversão de dependências junto com as demais letras do SOLID e demais boas práticas de desenvolvimento de software podem ser uma luz no seu caminho. E como você chegou até aqui acredito que seja esse o seu objetivo.

A inversão de dependência é um conceito muito importante para manter nosso *software* escalável e com fácil manutenção, mas como diz o ditado popular, uma andorinha só não faz verão, ele precisa sempre estar acompanhado dos outros princípios da [SOLID](https://en.wikipedia.org/wiki/SOLID).

Se você ficou com alguma dúvida sobre o conceito e/ou implementação da inversão de dependências ou de qualquer outro conceito utilizado nesse artigo, fique à vontade pra comentar aqui!