Não tenho mais pesadelos com Injeção de Dependência.
Eu penei bastante nos ultimos 2 anos para aprender sobre injeção de depêndencia por conta da crescente adoção no mundo do dotnet, eu foquei meus estudos com maior enfase no python, mas vai e vem eu voltava a estudar dotnet e outras linguagens para saber o que estava acontecendo no ecossistema da programação num contexto geral, e desde a vinda do dotnet core 2.0 e seus sucessores toda vez que eu tentava estudar dotnet eu precisava estudar Injeção de dependencia e inverção de controle.
A teoria é algo até simples de entender, não é tão complexa, a questão era a prática mesmo, até mesmo porque, chegou um momento em que o modelo de projeto padrão não esperava mais que a gente configurasse os nossos ORMs como entity framework como era antes, agora precisavamos configurar e injetar estes serviços, mas eu admito que não entendia bem como funcionava.
Eis a teoria… exageradamente simplificada.
Você possui uma classe qualquer por exemplo “usuario” e você precisa realizar “ações” nesta classe como cadastrar e listar usuários, então você precisará utilizar uma classe para acessar um banco de dados (não importa qual, mas vamos supor que se trata de um MySQL). Se você criar um objeto da sua classe do banco mysql dentro da sua classe “usuário” você esta criando uma dependencia direta da sua classe, então se algum dia mudar o sistema do banco de dados, ou apenas trocarem pra uma classe mais refinada, HAJA trabalho pra reescrever tudo eim… Então a melhor forma seria seguimentar em algumas etapas, fica mais verboso, mas pelo menos é menos dependente e facilita alguns processos futuros (e o código fica bonitão =D).
Nesse exemplo precisariamos de:
- Uma classe de dados (dataclass) para os dados do usuário.
- Uma “interface” para as operações dos usuários (é uma questão de contrato, assim garantimos que todas as classes com base nesse contrato terão o mesmo padrão).
- Uma (ou mais) classe de repositório.
- E uma classe de serviço.
Como podem ver saímos da situação de 2 classes para 4, mas vamos dar nomes a elas para identificarmos melhor.
- Usuario (dataclass)
- UsuarioRepository (interface)
- MySQLUsuarioRepository (Classe que herda a interface UsuarioRepository)
- UsuarioService (Classe que usa uma instancia baseada na UsuarioRepository não importa qual vai ser)
Viu só? dessa forma se trocarmos de banco do MySQL para o mongodb, a gente cria a classe MongoDBUsuarioRepository, altera a injeção da classe e pronto. Embora eu tenha mencionado bastante o dotnet pra falar de injeção de dependencia, vou colocar aqui um exemplo usando python por ser na minha opinião mais simples de ler, e daqui pro dotnet é uma questão de adaptação e estudo ok? Eu já expliquei todos os processos, portanto vou explicar no código apenas os pontos que “Fazem” a injeção de depência, para fazer esta injeção com o minimo de trabalho possível, vamos usar a lib python-inject, para instalhar basta apenas o comando:
pip install inject
Criei tudo num arquivo só, mas tenha em mente, que é uma boa prática e uma recomendação colocar cada classe em um arquivo separado. No exemplo vamos usar registro em memoria para brincar. Aqui vão os códigos:
Os imports (é um arquivo só não se esqueça).
from typing import List
from dataclasses import dataclass
from abc import ABC, abstractmethod
import inject
Nosso maravilhoso e completo banco de dados:
usuarios = []
Nossa dataclass:
@dataclass
class Usuario:
nome: str
idade: int
Nossa interface muito bem elaborada =D:
class UsuarioRepository(ABC):
@abstractmethod
def cadastrar_usuarios(self, usuario: Usuario) -> bool:
"""Cadastrar novo usuário."""
@abstractmethod
def listar_usuarios(self) -> List[Usuario]:
"""Listar usuários."""
Nosso repositório em memoria:
class InMemoryUsuarioRepository(UsuarioRepository):
def cadastrar_usuarios(self, usuario: Usuario) -> bool:
usuarios.append(usuario)
def listar_usuarios(self) -> List[Usuario]:
return list(usuarios)
Nosso serviço:
class UsuarioService:
@inject.autoparams()
def __init__(self, usuario_repository: UsuarioRepository) -> None:
self.usuario_repository = usuario_repository
def cadastrar(self, nome, idade):
self.usuario_repository.cadastrar_usuarios(Usuario(nome=nome, idade=idade))
def print_usuarios(self):
for usuario in usuarios:
print(f"nome: {usuario.nome} - Idade: {usuario.idade}")
E agora sim, a magia acontecendo:
def ioc_config(binder):
binder.bind(UsuarioRepository, InMemoryUsuarioRepository())
def register_ioc():
inject.configure(ioc_config)
Estas duas funções são responsáveis pela injeção acontecer, primeiramente notem, que na classe de serviço no construtor ele não está recebendo uma instancia da classe InMemoryUsuarioRepository e sim uma instancia da interface UsuarioRepository, é aqui que toda a injeção de dependencia se justifica, aqui poderiamos ter uma SQLServerUsuarioRepository ou uma CSVUsuarioRepository, desde que implemente a interface UsuarioRepository corretamente, não teremos problemas. E a função ioc_config é responsável por realizar essa injeção no nosso código e a função register_ioc, faz tudo acontecer por baixo dos panos de forma limpa. Com isso podemos rodar nosso código a partir dessa função:
def main():
register_ioc()
service = UsuarioService()
service.cadastrar("Afonso 001", "10")
service.cadastrar("Afonso 002", "11")
service.cadastrar("Afonso 003", "12")
service.cadastrar("Afonso 004", "13")
service.cadastrar("Afonso 005", "14")
service.print_usuarios()
if __name__ == "__main__":
main()
Eis a saida:
nome: Afonso 001 - Idade: 10
nome: Afonso 002 - Idade: 11
nome: Afonso 003 - Idade: 12
nome: Afonso 004 - Idade: 13
nome: Afonso 005 - Idade: 14
Finalmente consegui entender a dinamica kkkk, agora posso aplicar isso no meu dia a dia sem medo, já que trabalho com dotnet =D
Vou deixar aqui o código completo sem separações para consulta.
from typing import List
from dataclasses import dataclass
from abc import ABC, abstractmethod
import inject
usuarios = []
@dataclass
class Usuario:
nome: str
idade: int
class UsuarioRepository(ABC):
@abstractmethod
def cadastrar_usuarios(self, usuario: Usuario) -> bool:
"""Cadastrar novo usuário."""
@abstractmethod
def listar_usuarios(self) -> List[Usuario]:
"""Listar usuários."""
class InMemoryUsuarioRepository(UsuarioRepository):
def cadastrar_usuarios(self, usuario: Usuario) -> bool:
usuarios.append(usuario)
def listar_usuarios(self) -> List[Usuario]:
return list(usuarios)
class UsuarioService:
@inject.autoparams()
def __init__(self, usuario_repository: UsuarioRepository) -> None:
self.usuario_repository = usuario_repository
def cadastrar(self, nome, idade):
self.usuario_repository.cadastrar_usuarios(Usuario(nome=nome, idade=idade))
def print_usuarios(self):
for usuario in usuarios:
print(f"nome: {usuario.nome} - Idade: {usuario.idade}")
def ioc_config(binder):
binder.bind(UsuarioRepository, InMemoryUsuarioRepository())
def register_ioc():
inject.configure(ioc_config)
def main():
register_ioc()
service = UsuarioService()
service.cadastrar("Afonso 001", "10")
service.cadastrar("Afonso 002", "11")
service.cadastrar("Afonso 003", "12")
service.cadastrar("Afonso 004", "13")
service.cadastrar("Afonso 005", "14")
service.print_usuarios()
if __name__ == "__main__":
main()
Material de estudo: https://eskelsen.medium.com/precisamos-falar-de-inje%C3%A7%C3%A3o-invers%C3%A3o-de-depend%C3%AAncia-no-python-f79f53fa6f54
Origem do post: Eu escrevia um blog pessoal que eu mesmo havia desenvolvido a ferramenta e mantinha, mas para cortar custos removi o blog do ar e fiz um backup dos posts e vou adiciona-los aqui da forma como estavam lá com as correções que julgar necessária, claro, sempre respeitando o escopo do TabNews.