Gabriel / Ramos

⟨ pintor de pixel ⟩


  • Home
  • Blog
  • Desmistificando Injeção de Dependência

Desmistificando Injeção de Dependência

A verdade é mais simples do que parece

3 min. de leitura

Foto por Sara Kurfeß

TL;DR - receba como parâmetro componentes responsáveis por realizar efeitos colaterais na sua aplicação


Lidar com efeitos colaterais na sua aplicação é algo bem cotidiano.

Não é rara a necessidade de criar algum componente responsável por fazer alguma chamada HTTP, publicar um conteúdo em uma fila ou realizar qualquer mudança de estado em uma aplicação.

Em algum momento, principalmente quando relacionado a esses assuntos de "side-effects", você já deve ter se deparado com o termo injeção de dependência (ou "dependency injection") e muito provavelmente deve ter sido em um artigo com diversas classes/funções/métodos/containers e explicações um pouco mais complicadas do que deveriam sobre o tema.

Acontece que o fundamento sobre injeção de dependência é bem mais simples do que parece e pode facilitar seu dia-a-dia mais do que você imagina, principalmente se estiver em um ambiente que costuma fazer uso de testes automatizados.

Fundamento

Para explicar um pouco mais detalhadamente, acho que vale a pena exemplificarmos diretamente no código.

Imagine que em algum cenário da sua aplicação, você possui uma função que precisa fazer uma chamada get em uma API qualquer para acessar e formatar algum dado.

Algo como:

// módulo qualquer que fica responsável por fazer o fetching dos dados
const fetcher = require('fetcher');

async function getResources(url) {
  // acessa o dado que você precisa
  const response = await fetcher.get(url);

  // formata o dado de qualquer maneira
  const formattedData = response.data.any.map(item => item.value);

  // retorna o dado formatado
  return formattedData;
}

module.exports = getResources;

O elo de dependência existente nesse trecho de código fica claro pelo fato de o componente responsável por realizar a chamada ser declarado fora da sua função e utilizado dentro dela.

Injeção de dependência, fundamentalmente, é nada mais do que inserir determinado código num escopo onde você acessa valores globais ou declarados fora desse mesmo escopo.

Se realizarmos um pequeno ajuste nessa função, para utilizar injeção de dependência, teríamos algo como:

const fetcher = require('fetcher');

// inserimos a "injeção" ao receber fetcher como parâmetro
// podendo até setar o "default" como o fetcher do módulo que precisamos
async function getResources(url, fetcher = fetcher) {
  const response = await fetcher.get(url);

  const formattedData = response.data.any.map(item => item.value);

  return formattedData;
}

module.exports = getResources;

Vê a diferença? Agora você pode modificar ou passar um componente fetcher diferente ao executar sua função. A forma como os parâmetros são declarados podem variar dependendo da sua abordagem (poderia ser um objeto, por exemplo, ou qualquer forma que achar mais adequada).

Ou seja, você garante um código extensível para ser executado assim:

await getResources('http://my.api/v1/endpoint');

Ou assim:

// cria um componente "fake" para realizar essa chamada
const fakeFetcher = {
  get: () => Promise.resolve({})
};
await getResources('http://my.api/v1/endpoint', fetcher);

E quais as vantagens de utilizar injeção de dependência?

Além de evitar a utilização de variáveis e códigos globais ou fora do escopo declarado sem necessidade, você também ganha uma certa facilidade na hora testar unitariamente esses módulos.

Para testar primeiro cenário, onde o módulo fetcher era utilizado diretamente, você muito provavelmente precisaria fazer um mock desse módulo e manipular os retornos em todos os casos que precisar.

No segundo cenário, basicamente, você passa um objeto fetcher da forma como você precisa e consegue testar os casos de sucesso e falha muito mais tranquilamente, sem precisar fazer um mock manual dessa dependência.

Conforme a sua aplicação ou componente que realiza esses efeitos colaterais cresce (começar a fazer outros tipos de requisição, por exemplo), optar por essa abordagem pode ter ganhos que facilitam seu desenvolvimento (e seus testes) de forma bem clara e objetiva em longo prazo.