Inversão de controle e injeção de dependência são conceitos fundamentais no desenvolvimento de software. Eles nos permitem criar sistemas mais flexíveis, testáveis e escaláveis, reduzindo o acoplamento entre os componentes e aumentando a coesão. Neste post, vamos explorar a importância desses conceitos através de um exemplo prático utilizando o .NET 8.
De forma resumida, a inversão de controle é um princípio de design que diz que os módulos de alto nível não devem depender de módulos de baixo nível, mas sim de abstrações. Já a injeção de dependência é uma técnica que implementa esse princípio, permitindo que os componentes recebam suas dependências de fora, geralmente por meio de construtores ou propriedades, em vez de criá-las internamente. Dessa forma, a inversão de controle promove a desacoplação e a flexibilidade do sistema, enquanto a injeção de dependência é uma maneira de alcançar essa desacoplação na prática.
Para entendermos a importância, vamos começar com um exemplo bem simples que não utiliza a inversão de controle.
Criei uma Web Api do zero com o .NET 8 e excluí a controller e a classe WeatherForecast que vem de início como exemplo.
No lugar, criei uma classe que vai simular o nosso repositório, chamado ProdutoRepository:
É uma classe extremamente simples, com um método "RetornarProduto" que nos traz uma lista de strings, contendo "produto 1" e "produto 2". Num modelo real essa classe acessaria o banco e traria os produtos.
Em seguida, criamos a ProdutoController que vai chamar o repositório:
Agora vamos adicionar em nosso projeto de teste, uma classe para testar a nossa controller.
Aqui nós criamos uma instância da ProdutoController, em seguida chamamos o método Get() que é o nosso endpoint. Testamos se o retorno não é nulo, pois deve retornar algo. Testamos se o tipo de retorno é um "OkObjectResult", e em seguida se o que contém no "Value" dele corresponde a uma lista de strings. Depois, se há 2 valores nessa lista, e se eles são "Produto 1" e "Produto 2".
Todos os testes passam.
Agora vamos analisar, no nosso exemplo sabemos exatamente quais são esses 2 produtos que retorna, mas obviamente no mundo real não temos como garantir quais retornos irão vir do banco. Poderíamos utilizar um banco de dados de teste para esse caso exclusivamente, mas a ideia não é realizar um teste de integração, então seria interessante termos a possibilidade e flexibilidade para mockar esse repositório.
Outro problema, voltando para nossa Controller. A ProdutoController tem controle direto sobre a criação e o uso do ProdutoRepository, o que viola o princípio da inversão de controle. Isso porque a ProdutoController está diretamente acoplada à implementação específica do ProdutoRepository, tornando-a menos flexível e difícil de testar.
Se alterarmos algo na classe de Repository, poderíamos impactar diretamente o funcionamento da controller, uma vez que está fortemente acoplada à sua implementação.
Agora, vamos fazer a inversão de controle.
Primeiro, vamos criar uma interface para o nosso ProdutoRepository:
Criamos a interface, e fazemos nossa classe implementar ela. Após isso, vamos até a Controller utilizar a interface com a injeção de dependência, ao invés de instanciar a classe de implementação.
Criamos um campo que armazena uma instância de classe que implementa a interface IProdutoRepositoy. Só poderá ser utilizado dentro da controller, por é um campo privado, e "readonly", somente leitura, então só pode ser atribuído durante a inicialização ou no construtor da classe e não pode ser alterado posteriormente.
Nesse momento, a controller não conhece a implementação de "RetornarProduto()" que ela está chamando.
Agora em nossa classe de teste, podemos mockar esse repositório.
System.InvalidOperationException: Unable to resolve service for type '<interface>' while attempting to activate |
O que isso significa? Nós não registramos nosso serviço no container de injeção de dependência. Então vamos fazê-lo. Como estou no .NET 8, isso é feito na classe Program. No .NET 5 seria na Startup.
0 Comentários