Injeção de dependências em um paradigma funcional

DesenvolvimentoGerenciamento de ProjetosNegóciosSoftwareTecnologia

Você já ouviu falar em injeção de dependências na programação? Se sim, muito provavelmente você já se deparou com exemplos e implementações que utilizam esse padrão de projeto utilizando classes, correto?

Pois bem, hoje o Raphael Oliveira, Desenvolvedor aqui na nata.house, vai explicar pra gente sobre esse padrão de projeto, e também trazer exemplos de como implementá-lo utilizando o paradigma funcional comparado ao orientado a objetos.

Bora conferir? 

O que é injeção de dependência 

Para começar, caso você não conheça o que é injeção de dependência, uma explicação breve:

Injeção de dependência é um padrão de projeto que tem como objetivo manter os componentes do seu código desacoplados.

Um exemplo: 

Vamos pensar em um cenário em que você precisa salvar dados dos seus usuários como nome, email, etc.. Geralmente a primeira coisa que pensamos quando nos deparamos com uma demanda dessa, é sobre o banco de dados. Qual banco vou usar? mysql, postgres, sqlite. Qual/quais tabelas irei salvar essas informações?

Porém,  quando pensamos dessa forma, estamos criando uma relação direta entre nossa aplicação e o banco de dados. Ou seja, a aplicação tem o banco de dados como uma dependência e só irá funcionar caso exista o banco de dados.

Utilizando classes

Vamos ver um exemplo utilizando classes, onde criamos um controller que possui um método handler, que pega alguns valores de entrada (email e nome do usuário) e salva esses dados em um banco de dados:

Utilizando funções, ficaria algo semelhante a isso:

Nesse caso, nosso controller tem uma dependência direta do banco de dados, ou seja, para nosso controller fazer o que precisa, ele necessita do banco de dados. 

Nosso controller tem um acoplamento com o banco de dados.

E isso é ruim? Tudo vai depender do seu contexto! Mas saiba que dessa forma, fica mais difícil realizar alterações futuras no código. 

Por exemplo: e se no futuro eu quiser salvar os dados do usuário em um arquivo csv ou em um outro banco de dados de algum serviço externo, como da aws por exemplo? Com essa implementação eu precisaria mudar a implementação do meu controller para realizar essas alterações:

Nesse caso, se eu quiser que meu controller não tenha acoplamento com o componente responsável por salvar o usuário, eu posso utilizar a injeção de dependência para isso.

A primeira tarefa quando você deseja implementar o padrão de projeto de injeção de dependência é criar um contrato, uma interface, que vai me dizer exatamente oque espera da dependência a ser injetada.

Nesse exemplo criamos uma interface que irá definir quais são os parâmetros a serem recebidos e qual é o retorno, que nesse caso, ele espera o nome e email do usuário como parâmetros e retorna uma promise.

Dica!

Quando estamos trabalhando com linguagens não tipadas, também é possível implementar a injeção de dependências, porém não será possível definir os contratos programaticamente entre essas possíveis injeções que o componente espera. 

Desse modo, em linguagens não tipadas, deve existir um cuidado maior ao implementar esse padrão. Talvez utilizar testes unitários para garantir o comportamento esperado de cada componente seja um bom caminho 🙂

Depois de definirmos nossa interface, iremos utilizar ela como tipagem para o valor a ser injetado na nossa classe/função, ficaria algo assim utilizando classes:

E utilizando funções:

O que mudou agora? Basicamente o controller não sabe como o usuário vai ser salvo (se vai ser em arquivo, no banco de dados, aws, google, etc..), o controller só sabe sobre a assinatura da função que ele tem que chamar para salvar o usuário (contrato/interface).

Sobre o paradigma funcional, oque mudou foi que a dependência é injetada via props, enquanto no paradigma orientado a objetos as dependências geralmente são injetadas no construtor.

Mas acabou? Ainda não! Quem for usar o nosso controller agora vai precisar passar/injetar a dependência responsável por salvar o usuário, desse modo nosso sistema fica flexível para usar o mesmo controller, mas com diferentes formas de se salvar o usuário. 

Nesse contexto, o papel do controller é controlar o fluxo de execução dos meus dados e repassá-los quando necessário.

Implementações do contrato

Agora vamos criar implementações do contrato que criamos anteriormente para que possamos injetá-los no nosso controlle. Vamos começar criando a implementação da interface que salva nosso usuário em um arquivo csv:

Agora vamos criar outra implementação que salvará o usuário no banco de dados:

Dessa forma, seria possível criar infinitas extensões/implementações que tem o papel de salvar usuário, seja onde for.

Finalizando, conseguimos utilizar nosso controller injetando qualquer uma das dependências criadas acima. Vamos ver o exemplo injetando a dependência que salva o usuário em csv:

Agora injetando a implementação que salva o usuário no banco de dados:

Dessa forma, fica fácil injetar qualquer implementação de formas de salvar o usuário, e o controller não precisa ter ideia de como esse usuário é salvo. Ou seja, meu controller está desacoplado. 

Isso além de facilitar a manutenção do sistema caso precise alterar a forma de salvar o usuário, também ajuda bastante em testes unitários, pois você consegue facilmente injetar dependências fakes para serem utilizadas durante a execução dos seus testes.

E outra observação bem importante é: observando os exemplos, percebemos que mesmo injetando diferentes implementações no meu controller, eu não mudo a forma como utilizo ele. Ou seja, todo meu sistema que for utilizar o controller sempre utilizará ele dessa maneira e não irá precisar saber como ou onde o usuário será salvo, só precisam saber se o usuário foi salvo com sucesso:

___

___

Nossa especialidade é construir aplicações com excelência que realmente contribuam para o desenvolvimento e crescimento dos nossos clientes. Somos defensores de boas práticas que promovem o desenvolvimento com qualidade, e por isso, a organização está presente em todos os nossos processos. 

É hora da sua equipe de tecnologia partir para o próximo nível e nós podemos ajudar. 

Quer saber mais sobre como a nata.house pode contribuir para o crescimento do seu negócio? Fale com um dos nossos especialistas! 

Receba conteúdos sobre inovação digital, novas tecnologias, design e desenvolvimento.

Entre em contato

Telefones

+55 31 98426-5166

+55 31 4042-1001

Endereço

R. Paraíba, 330, sala 1006

Belo Horizonte - MG - Brasil