Injeção de dependência é um padrão de design em que objetos ou serviços (aqui, dependências) são fornecidos a uma classe de fora, em vez de a classe criá-los internamente. Isso separa os componentes.
Usar injeção de dependência em Python tem várias vantagens, como:
Modularidade: Seu código é dividido em partes menores e reutilizáveis.
Testabilidade: É mais fácil testar seu código porque você pode trocar a parte real por uma simulada.
Manutenção: Você pode atualizar ou mudar partes do seu código sem mexer no resto do sistema.
Neste tutorial, vou mostrar como usar injeção de dependência em Python com exemplos simples e práticos.
Entendendo a injeção de dependência em Python
Pra entender como implementar a injeção de dependência, é essencial que você entenda os princípios fundamentais que a regem.
A injeção de dependência é baseada em algo chamado princípio da inversão de controle (IoC), que significa que, em vez de uma classe criar e gerenciar suas dependências, ela as recebe de uma fonte externa. Isso ajuda a focar em uma única tarefa e deixa seu código mais limpo.
Injetar dependências em Python inclui essas maneiras comuns, embora existam outras:
Injeção do construtor: Passa as dependências pelo construtor da classe.
Injeção do setter: Defina as dependências usando um método setter depois que o objeto for criado.
Injeção de método: Passa as dependências direto pro método que precisa delas.
Por que usar injeção de dependência em Python?
Como desenvolvedor, entender e implementar a injeção de dependências pode fazer uma grande diferença no design do seu código. Vamos ver algumas vantagens da injeção de dependência.
Modularidade, reutilização e flexibilidade
A injeção de dependência permite dividir seu código em componentes menores e mais focados, proporcionando maior flexibilidade e modularidade.
Cada parte ou componente faz uma tarefa específica e depende de outras coisas, o que facilita reutilizá-las em diferentes partes do seu aplicativo ou do aplicativo de outra pessoa.
Por exemplo, se você tem uma classe que manda mensagens. Em vez de criar uma lógica de e-mail ou SMS diretamente no seu serviço, você pode usar uma classe de remetente de e-mail ou SMS.
Agora, sua classe que manda mensagens não liga pra como a mensagem é enviada, só usa o remetente que foi fornecido pra ela. Isso quer dizer que você pode usar seu remetente de mensagens com diferentes remetentes de e-mail ou SMS.
Manutenção e testabilidade
Quando você usa injeção de dependência, dá pra trocar facilmente as dependências reais por dados simulados durante os testes. Isso facilita escrever testes unitários sem depender de APIs ou bancos de dados.
Imagina, só pra pensar, que você tem uma aula que salva dados em um banco de dados. Para fins de teste, você pode usar um banco de dados falso em vez de um real para evitar que o seu teste fique ligado ao banco de dados de produção.
A injeção de dependência permite que você modifique partes específicas do seu código sem quebrar todo o sistema. Isso é super útil quando você precisa trocar de provedor de pagamento em um aplicativo, tipo do Stripe para o PayPal, sem mexer no resto do seu código.
Inversão de controle e dependências trocáveis
A ideia da inversão de controle é a seguinte: a injeção de dependência vai passar a responsabilidade de criar e gerenciar dependências para uma fonte externa.
Isso quer dizer que você pode trocar facilmente uma implementação por outra; por exemplo, se você tem uma classe ReportGenerator que formata e exporta relatórios.
Em vez de a classe decidir o formato de exportação, você pode colocar uma classe exportadora, como PDFExporter, ExcelExporter ou CSVExporter, nela.
A classe ` ReportGenerator ` não liga pra como a exportação rola, então, pra mudar um formato, é só trocar o exportador que você injeta, sem mexer no ` ReportGenerator `.
Implementando injeção de dependência em Python
Agora que você já tem uma ideia clara do que é injeção de dependência e por que ela é útil, vamos ver como você pode implementá-la em Python. Vamos começar com o método mais simples: injeção manual de dependências.
Injeção manual de dependências
Na injeção de dependência manual, você mesmo cria as dependências e as passa para a classe ou função que precisa delas.
Esse método funciona bem para pequenas aplicações, mas conforme sua aplicação cresce, você precisa usar uma estrutura de injeção de dependência para evitar erros.
Aqui tá um exemplo de código sem injeção de dependência
classEmailService:defsend_email(self, message):print(f"Sending email: {message}")classUserNotifier:def__init__(self):
self.email_service = EmailService()# Creates its own dependencydefnotify(self, message):
self.email_service.send_email(message)
notifier = UserNotifier()
notifier.notify("Welcome!")Tecnologia
No exemplo acima, UserNotifier está bem ligado a EmailService. Não é fácil trocar ou simular um objeto de serviço ( EmailService ) para testes.
Aqui está outra versão, mas com injeção de dependência manual.
A versão acima é flexível e permite que você insira vários serviços de mensagens; você também pode substituir EmailService por uma simulação nos testes.
Usando a estrutura do injetor de dependências
À medida que sua aplicação cresce, fica mais complicado e confuso gerenciar as dependências.
Singleton garante que só exista uma instância de EmailService
Factory cria um novo objeto UserNotifier com o EmailService injetado automaticamente.
Mecanismo de fiação
Você pode usar decoradores como @inject pra reduzir a necessidade de passar dependências manualmente.
Aqui está outra versão do exemplo anterior:
from dependency_injector import containers, providers
# Define servicesclassEmailService:defsend_email(self, message):print(f"Sending email: {message}")classUserNotifier:def__init__(self, email_service: EmailService):
self.email_service = email_service
defnotify(self, message):
self.email_service.send_email(message)# Create containerclassContainer(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
user_notifier = providers.Factory(UserNotifier, email_service=email_service)from dependency_injector.wiring import inject, Provide
@injectdefmain(notifier: UserNotifier = Provide[Container.user_notifier]):
notifier.notify("Hello!")if __name__ =="__main__":
container = Container()
container.wire(modules=[__name__])# This wires the current module
main()Tecnologia
Na versão atual;
Sem adicionar manualmente a dependência, o decorador ` @inject ` injeta automaticamente a dependência da função ou classe.
A classe ` Provide ` diz ao ` dependency-injector ` qual dependência deve ser injetada.
Essa abordagem elimina a necessidade de dependências de fiação manual do contêiner.
Isso é útil em aplicações maiores, onde os serviços podem depender de vários componentes (daí a necessidade de inicializá-los automaticamente).
Usando injeção de dependência em frameworks Python populares
A maioria das estruturas Python modernas facilita o trabalho com injeção de dependência, permitindo que você escreva um código mais limpo e testável.
Nesta seção, você vai aprender como a injeção de dependência é aplicada no Flask, Django e FastAPI.
Injeção de dependência no Flask
O Flask não tem suporte integrado para injeção de dependências, mas você pode implementar isso usando a biblioteca dependency-injector seguindo esses passos.
Passo 2: Configure seu contêiner de injeção de dependências
from dependency_injector import containers, providers
classContainer(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)Tecnologia
Passo 3: Crie o aplicativo Flask e injete as dependências.
from flask import Flask, request
from dependency_injector.wiring import inject, Provide
container = Container()
app = Flask(__name__)@app.route("/greet")@injectdefgreet(greeting_service: GreetingService = Provide[Container.greeting_service]):
name = request.args.get("name","World")return greeting_service.get_greeting(name)
container.wire(modules=[__name__])if __name__ =="__main__":
app.run(debug=True)Tecnologia
A partir do código acima:
A função name = request.args.get(“name”, “World”) pega o name da string de consulta e, se não tiver, usa ”World” por padrão.
return greeting_service.get_greeting(name) chama o método no serviço injetado e retorna uma mensagem de saudação.
container.wire(modules=[__name__]) diz ao injetor de dependências para procurar neste módulo por decoradores de dependência ( @inject ) e marcadores de dependência ( Provide[...] ) para conectá-los aos provedores reais.
Passo 4: Teste o aplicativo
Execute o aplicativo no seu terminal usando o comando python .py e acesse o URL http://localhost:5000/greet?name=Jacob.
Você deve ver o seguinte.
Injeção de dependência no Django
Assim como o Flask, o Django não oferece injeção de dependência integrada. Mas você pode usar as visualizações baseadas em classes, o middleware e a estrutura do aplicativo para integrar a injeção de dependências manualmente ou usando a biblioteca dependency-injector.
Aqui está um passo a passo para implementar a injeção de dependência no Django.
Passo 1: Defina o seu serviço
Crie um serviço simples com a lógica do seu negócio em um novo arquivo chamado “ services.py ” dentro da pasta do seu aplicativo.
Inicie o servidor usando o comando uvicorn main:app –reload e acesse o URL http://localhost:8000/greet?name=FastAPI. Você deve ver a seguinte resposta.
Comparando frameworks de injeção de dependências Python
Existem várias estruturas de dependência Python, cada uma com seus pontos fortes, sintaxe e casos de uso. Escolher a estrutura certa depende do tamanho e da organização do seu projeto.
Aqui tá uma comparação de algumas estruturas comuns de injeção de dependência Python.
Oferece injeção de dependência para visualizações e middleware.
Depende de injector
Padrões avançados de implementação
À medida que o código da sua aplicação cresce, fica mais difícil manter a injeção de dependências.
Você pode usar a injeção de dependência avançada para lidar com tarefas como recursos no escopo da solicitação, limpeza e separação de interesses.
Dependências com escopo
As dependências com escopo permitem controlar por quanto tempo uma dependência fica ativa, por exemplo, quando você quer um novo objeto por solicitação, um objeto compartilhado durante toda a vida útil do aplicativo ou a limpeza automática de recursos, como conexões de banco de dados.
A maioria das estruturas de injeção de dependências suporta escopos como:
Singleton: Uma instância para todo o aplicativo.
Fábrica: Uma nova instância toda vez que é injetada.
Local na thread ou na solicitação: Uma única instância por thread/solicitação.
Recurso: Gerenciado com lógica de configuração e desmontagem.
Aqui vai um exemplo de dependências com escopo usando contêineres aninhados com o ` dependency-injector`.
from dependency_injector import containers, providers, resources
# A simple resource that needs setup and cleanupclassMyResource:def__enter__(self):print("Setting up the resource")return self
def__exit__(self, exc_type, exc_val, exc_tb):print("Cleaning up the resource")# Create a container to manage the resourceclassMyContainer(containers.DeclarativeContainer):
resource = providers.Resource(MyResource)# Using the resource with a context manager
container = MyContainer()with container.resource()as r:print("Using the resource")Tecnologia
No exemplo acima, MyResource imita uma classe que precisa de configuração e desativação, como abrir/fechar um arquivo ou banco de dados.
O container fornece o recurso usando providers.Resource e, quando você usa with container.resource(), ele entra e sai do contexto corretamente.
Esse padrão garante que recursos, como identificadores de arquivos, conexões de banco de dados ou sessões de rede, sejam gerenciados de forma organizada.
Imagina um endpoint de API que precisa de uma sessão de banco de dados para a solicitação atual ou um logger com contexto específico da solicitação.
Com dependências com escopo, a estrutura cria um contêiner com escopo de solicitação quando a solicitação começa a resolver todas as dependências desse escopo.
Quando a solicitação termina, qualquer limpeza necessária, como fechar sessões do banco de dados, rola automaticamente. Isso garante que os estados não sejam compartilhados entre solicitações e evita vazamentos de recursos.
Injeção assíncrona
Para lidar com tarefas vinculadas a E/S de forma eficiente, você precisa de execução assíncrona. Os sistemas de injeção de dependência em frameworks como FastAPI e dependency-injector oferecem injeção assíncrona usando async/await.
async defasync resources Injeção assíncrona é quando a gente injeta dependências que são funções assíncronas ou gerencia coisas assíncronas, tipo conexões de banco de dados assíncronas, APIs externas ou tarefas em segundo plano.
Em vez de resolver tudo ao mesmo tempo, o sistema de injeção de dependências espera que tudo seja resolvido antes de fazer a injeção.
Aqui vai um exemplo de dependência assíncrona no FastAPI:
O FastAPI cuida automaticamente da resolução assíncrona de get_user e injeta o resultado.
dependency-injector também dá suporte ao gerenciamento assíncrono do ciclo de vida usando provedores de gerenciamento de recursos ( AsyncResource ).
from dependency_injector import containers, providers, resources
classAsyncDB:asyncdefconnect(self):print("Connected")return self
asyncdefdisconnect(self):print("Disconnected")classAsyncDBResource(resources.AsyncResource):asyncdefinit(self)-> AsyncDB:
db = AsyncDB()returnawait db.connect()asyncdefshutdown(self, db: AsyncDB):await db.disconnect()classContainer(containers.DeclarativeContainer):
db = providers.Resource(AsyncDBResource)# Usageasyncdefmain():
container = Container()await container.init_resources()
db =await container.db()# Get the AsyncDB instance# Use db here...await container.shutdown_resources()Tecnologia
No código acima:
resources.AsyncResource é usado pra lógica de configuração/desconfiguração assíncrona.
await container.init_resources() inicializa todos os recursos e chama init.
await container.shutdown_resources() limpa todos os recursos.
await container.db() pega a instância real dos recursos, que é AsyncDB.
Usar injeção de dependência assíncrona ajuda a manter o desempenho da sua aplicação, evitando E/S síncronas em rotas assíncronas e eliminando a necessidade de aguardar manualmente as dependências na sua lógica.
Implicações de segurança
Embora a injeção de dependência melhore a modularidade e a testabilidade, ela pode trazer riscos de segurança se não for bem gerenciada.
Um risco importante é a confusão de dependências, em que pacotes maliciosos imitam os legítimos. Por exemplo, os invasores carregaram pacotes no PyPI com nomes usados em sistemas internos da empresa para enganar os aplicativos e fazê-los usar a versão maliciosa.
Para ficar seguro, nunca injete componentes não confiáveis ou controlados pelo usuário e valide as fontes de configuração, como variáveis de ambiente, antes de injetá-las.
Use os arquivos “ requirements.txt ” ou “ poetry.lock ” pra bloquear versões exatas e automatizar a verificação de segurança em caso de alterações nas dependências. Além disso, certifique-se de verificar regularmente se tem vulnerabilidades nas suas dependências.
Centralizar o controle das suas dependências também é essencial, pois garante a consistência entre as suas aplicações e facilita a aplicação de patches de segurança ou atualizações de versão. Isso ajuda a evitar “dependências ocultas” que podem aparecer de várias maneiras no código.
Tem várias ferramentas que podem detectar riscos e melhorar a sua segurança:
Snyk: Procura vulnerabilidades conhecidas em pacotes Python e sugere correções.
Segurança: Verifica se tem pacotes inseguros e vulnerabilidades no código.
Bandido: Um analisador de código estático para Python focado em questões de segurança.
Aplicações no mundo real
A injeção de dependência não é só um padrão teórico; ela é usada em vários sistemas de produção pra gerenciar a complexidade, melhorar a flexibilidade e simplificar os testes.
Como configurar o serviço da Web
Nas aplicações web modernas, a injeção de dependências tem um papel super importante no gerenciamento de serviços padrão, como autenticação, registro em log e acesso a bancos de dados.
Estruturas como FastAPI são super importantes pra resolver rotas e dependências.
O exemplo abaixo mostra como é uma autenticação centralizada no FastAPI.
from fastapi import Depends, HTTPException
defget_current_user(token:str= Depends(oauth2_scheme)):
user = verify_token(token)ifnot user:raise HTTPException(status_code=401)return user
@app.get("/protected")defprotected_route(user:dict= Depends(get_current_user)):return{"message":f"Hello {user['name']}"}Tecnologia
No código acima, get_current_user() é injetado como uma dependência de rota, mas a lógica para verificar o token é centralizada e reutilizável.
Você também pode fazer o mesmo para o acesso ao banco de dados, de modo que as sessões do banco de dados sejam gerenciadas e limpas automaticamente.
Aqui tá um exemplo de como você pode usar a injeção de dependência pra centralizar o acesso ao banco de dados.
from sqlalchemy.ext.asyncio import AsyncSession
asyncdefget_db_session()-> AsyncSession:asyncwith async_session()as session:yield session
@app.get("/items")asyncdefread_items(db: AsyncSession = Depends(get_db_session)):returnawait db.execute("SELECT * FROM items")Tecnologia
No código acima, o get_db_session é um provedor de dependências. Quando a rota ` read_items ` é chamada, o FastAPI automaticamente injeta o parâmetro ` db `, permitindo que a rota acesse o banco de dados sem precisar gerenciar manualmente a configuração ou desativação da sessão.
Desenvolvimento orientado por testes
A injeção de dependência facilita a alteração das dependências que os componentes recebem, o que é importante durante os testes.
Você pode trocar serviços reais por simulados durante os testes,deixando seu código mais modular e fácil de testar.
O FastAPI e o dependency-injector aceitam substituir dependências na hora do teste.
Aqui tá um exemplo no FastAPI.
from fastapi.testclient import TestClient
from main import app, get_db_session
defoverride_db():return TestDBSession()
app.dependency_overrides[get_db_session]= override_db
client = TestClient(app)
response = client.get("/items")assert response.status_code ==200Tecnologia
No exemplo de código acima, get_db_session é substituído por um banco de dados simulado durante os testes; por isso, não é preciso corrigir ou alterar o código de produção.
O mesmo também em depedency-injector, você pode substituir seu banco de dados real por um banco de dados simulado para fins de teste, como no exemplo abaixo.
from containers import Container
deftest_service_behavior():
container = Container()
container.db.override(providers.Factory(TestDB))
service = container.service()assert service.get_data()=="mocked"Tecnologia
O método ` override() ` substitui a dependência real por uma simulação, que é limpa, controlada e reversível.
Aqui estão algumas estratégias que você deve usar para substituir dependências:
Injeção do construtor: Certifique-se de passar os mocks diretamente para o construtor.
Substituições da estrutura: Use ferramentas de substituição integradas, como FastAPI.dependency_overrides no FastAPI ou dependency_injector.override() no dependency-injector.
Jogos: Use estruturas de teste como pytest para criar simulações reutilizáveis e inseri-las por meio de fixtures.
Melhores práticas e anti-padrões
Embora a injeção de dependência tenha como objetivo tornar o código modular e fácil de testar, ela pode causar bugs ou tornar o código mais complexo.
O que a gente recomenda
Aqui estão algumas dicas que você deve seguir ao criar dependências:
Programe para interfaces, não para implementações: Defina suas dependências usando classes base abstratas ou interfaces em vez de implementações complexas. Isso facilita o teste do seu código e também incentiva o uso de simulações em testes unitários.
Prefira composição em vez de herança: Em vez de criar hierarquias de classes complicadas, monte objetos usando dependências injetadas pra manter os componentes pequenos e focados.
Configuração centralizada de dependências: Use um único contêiner ou módulo pra definir e gerenciar dependências, facilitando a substituição e os testes.
Evite dependências circulares: Planeje bem seu gráfico de dependências pra evitar referências circulares usando funções de fábrica ou provedores pra adiar a instanciação.
Erros comuns
Aqui estão algumas armadilhas comuns que você deve evitar ao usar injeção de dependência.
Anti-padrão do localizador de serviços: Evite usar um contêiner global para buscar dependências dinamicamente, pois isso pode ocultar dependências e tornar o código complicado de entender e testar.
Injeção excessiva: Evite colocar muitos serviços em uma única classe ou função pra evitar que seu código fique com construtores pesados e lógica difícil de testar.
Gestão inadequada do escopo e desvio de recursos públicos: Certifique-se de sempre usar o escopo certo pra evitar compartilhar o estado entre usuários ou testes.
Teste de injeção de dependência em Python
A injeção de dependência facilita a inserção de objetos simulados nos seus componentes, simplificando assim os testes unitários. Isso permite separar os componentes básicos dos específicos do teste.
Com a injeção de dependência, os testes são determinísticos, não têm efeitos colaterais e resultam em uma execução mais rápida dos testes.
Aqui tá um exemplo de como você pode usar a injeção de dependência pra injetar versões simuladas, stubs ou falsas dessas dependências nos seus testes.
classTestEmailService:defsend_email(self, to, message):
self.sent_to = to
deftest_notifier_sends_email():
test_email = TestEmailService()
notifier = Notifier(test_email)
notifier.alert("[email protected]")assert test_email.sent_to =="[email protected]"Tecnologia
Certifique-se de usar injeção de dependência com frequência e evite monkey patching para facilitar a manutenção e evitar alterações significativas quando nomes internos forem alterados.
Conclusão
Embora você possa não precisar de injeção de dependência no começo para projetos pequenos, conforme o projeto cresce e se expande, você pode aproveitar a estrutura e a modularidade oferecidas pela injeção de dependência.
Olhando para o futuro, a injeção de dependência em Python parece promissora, e a gente pode esperar que ela continue com um sistema de tipos melhorado e um gerenciamento de ciclo de vida assíncrono aprimorado.
Dominar a injeção de dependência é essencial para se tornar um desenvolvedor Python fluente. Mas não para por aí; muitos outros conceitos avançados podem elevar suas habilidades em Python.
Para te ajudar a acelerar sua jornada de aprendizado, aqui estão alguns dos nossos recursos que você deve conferir.
Instrutor experiente em ciência de dados e bioestatístico com experiência em Python, R e machine learning.
Perguntas frequentes
O que é injeção de dependência?⌄
A injeção de dependência é um padrão de design que te ajuda a escrever um código mais limpo, criando dependências dentro de uma classe, que podem ser passadas de fora, tornando seu código mais fácil de gerenciar e modificar.
Quais são as principais vantagens da injeção de dependência?⌄
A injeção de dependência deixa seu código modular, testável e fácil de manter.
Como posso implementar a injeção de dependência em Python?⌄
Você pode implementar a injeção de dependência em Python manualmente ou usando bibliotecas como dependency_injector.
O que é injeção assíncrona?⌄
async defasync resources Injeção assíncrona é quando a gente injeta dependências que são funções assíncronas ou gerencia coisas assíncronas, tipo conexões de banco de dados assíncronas, APIs externas ou tarefas em segundo plano.
Quais são os riscos de segurança da injeção de dependência e como posso lidar com eles?⌄
Um risco importante é a confusão de dependências, em que pacotes maliciosos imitam os legítimos. Para evitar isso, você pode usar ferramentas como Snyk, Xygeni, Safety ou Bandit para verificar se há vulnerabilidades no seu código e nos seus pacotes.
Aumente o nível de suas habilidades em Python com estes cinco desafios de codificação em Python. Faça um teste para ver se você consegue completar um em uma semana!
Este guia completo ensina os fundamentos do desenvolvimento backend com Python. Aprenda conceitos básicos, frameworks e boas práticas para começar a criar aplicações web.
Injeção de dependência é um padrão de design em que objetos ou serviços (aqui, dependências) são fornecidos a uma classe de fora, em vez de a classe criá-los internamente. Isso separa os componentes.
Usar injeção de dependência em Python tem várias vantagens, como:
Neste tutorial, vou mostrar como usar injeção de dependência em Python com exemplos simples e práticos.
Entendendo a injeção de dependência em Python
Pra entender como implementar a injeção de dependência, é essencial que você entenda os princípios fundamentais que a regem.
A injeção de dependência é baseada em algo chamado princípio da inversão de controle (IoC), que significa que, em vez de uma classe criar e gerenciar suas dependências, ela as recebe de uma fonte externa. Isso ajuda a focar em uma única tarefa e deixa seu código mais limpo.
Injetar dependências em Python inclui essas maneiras comuns, embora existam outras:
Por que usar injeção de dependência em Python?
Como desenvolvedor, entender e implementar a injeção de dependências pode fazer uma grande diferença no design do seu código. Vamos ver algumas vantagens da injeção de dependência.
Modularidade, reutilização e flexibilidade
A injeção de dependência permite dividir seu código em componentes menores e mais focados, proporcionando maior flexibilidade e modularidade.
Cada parte ou componente faz uma tarefa específica e depende de outras coisas, o que facilita reutilizá-las em diferentes partes do seu aplicativo ou do aplicativo de outra pessoa.
Por exemplo, se você tem uma classe que manda mensagens. Em vez de criar uma lógica de e-mail ou SMS diretamente no seu serviço, você pode usar uma classe de remetente de e-mail ou SMS.
Agora, sua classe que manda mensagens não liga pra como a mensagem é enviada, só usa o remetente que foi fornecido pra ela. Isso quer dizer que você pode usar seu remetente de mensagens com diferentes remetentes de e-mail ou SMS.
Manutenção e testabilidade
Quando você usa injeção de dependência, dá pra trocar facilmente as dependências reais por dados simulados durante os testes. Isso facilita escrever testes unitários sem depender de APIs ou bancos de dados.
Imagina, só pra pensar, que você tem uma aula que salva dados em um banco de dados. Para fins de teste, você pode usar um banco de dados falso em vez de um real para evitar que o seu teste fique ligado ao banco de dados de produção.
A injeção de dependência permite que você modifique partes específicas do seu código sem quebrar todo o sistema. Isso é super útil quando você precisa trocar de provedor de pagamento em um aplicativo, tipo do Stripe para o PayPal, sem mexer no resto do seu código.
Inversão de controle e dependências trocáveis
A ideia da inversão de controle é a seguinte: a injeção de dependência vai passar a responsabilidade de criar e gerenciar dependências para uma fonte externa.
Isso quer dizer que você pode trocar facilmente uma implementação por outra; por exemplo, se você tem uma classe
ReportGeneratorque formata e exporta relatórios.Em vez de a classe decidir o formato de exportação, você pode colocar uma classe exportadora, como
PDFExporter,ExcelExporterouCSVExporter, nela.A classe `
ReportGenerator` não liga pra como a exportação rola, então, pra mudar um formato, é só trocar o exportador que você injeta, sem mexer no `ReportGenerator`.Implementando injeção de dependência em Python
Agora que você já tem uma ideia clara do que é injeção de dependência e por que ela é útil, vamos ver como você pode implementá-la em Python. Vamos começar com o método mais simples: injeção manual de dependências.
Injeção manual de dependências
Na injeção de dependência manual, você mesmo cria as dependências e as passa para a classe ou função que precisa delas.
Esse método funciona bem para pequenas aplicações, mas conforme sua aplicação cresce, você precisa usar uma estrutura de injeção de dependência para evitar erros.
Aqui tá um exemplo de código sem injeção de dependência
No exemplo acima,
UserNotifierestá bem ligado aEmailService. Não é fácil trocar ou simular um objeto de serviço (EmailService) para testes.Aqui está outra versão, mas com injeção de dependência manual.
A versão acima é flexível e permite que você insira vários serviços de mensagens; você também pode substituir
EmailServicepor uma simulação nos testes.Usando a estrutura do injetor de dependências
À medida que sua aplicação cresce, fica mais complicado e confuso gerenciar as dependências.
A estrutura
dependency-injectorestrutura dá uma abordagem organizada.Arquitetura do provedor de contêineres
A estrutura do
dependency-injectorfunciona com base numa arquitetura de fornecedor de contêineres.No exemplo acima:
Containerdefine como as instâncias são criadasSingletongarante que só exista uma instância deEmailServiceFactorycria um novo objetoUserNotifiercom oEmailServiceinjetado automaticamente.Mecanismo de fiação
Você pode usar decoradores como
@injectpra reduzir a necessidade de passar dependências manualmente.Aqui está outra versão do exemplo anterior:
Na versão atual;
Sem adicionar manualmente a dependência, o decorador `
@inject` injeta automaticamente a dependência da função ou classe.A classe `
Provide` diz ao `dependency-injector` qual dependência deve ser injetada.Essa abordagem elimina a necessidade de dependências de fiação manual do contêiner.
Isso é útil em aplicações maiores, onde os serviços podem depender de vários componentes (daí a necessidade de inicializá-los automaticamente).
Usando injeção de dependência em frameworks Python populares
A maioria das estruturas Python modernas facilita o trabalho com injeção de dependência, permitindo que você escreva um código mais limpo e testável.
Nesta seção, você vai aprender como a injeção de dependência é aplicada no Flask, Django e FastAPI.
Injeção de dependência no Flask
O Flask não tem suporte integrado para injeção de dependências, mas você pode implementar isso usando a biblioteca
dependency-injectorseguindo esses passos.Passo 1: Defina seus serviços
Passo 2: Configure seu contêiner de injeção de dependências
Passo 3: Crie o aplicativo Flask e injete as dependências.
A partir do código acima:
A função
name = request.args.get(“name”, “World”)pega onameda string de consulta e, se não tiver, usa”World”por padrão.return greeting_service.get_greeting(name)chama o método no serviço injetado e retorna uma mensagem de saudação.container.wire(modules=[__name__])diz ao injetor de dependências para procurar neste módulo por decoradores de dependência (@inject) e marcadores de dependência (Provide[...]) para conectá-los aos provedores reais.Passo 4: Teste o aplicativo
Execute o aplicativo no seu terminal usando o comando
python .pye acesse o URLhttp://localhost:5000/greet?name=Jacob.Você deve ver o seguinte.
Injeção de dependência no Django
Assim como o Flask, o Django não oferece injeção de dependência integrada. Mas você pode usar as visualizações baseadas em classes, o middleware e a estrutura do aplicativo para integrar a injeção de dependências manualmente ou usando a biblioteca
dependency-injector.Aqui está um passo a passo para implementar a injeção de dependência no Django.
Passo 1: Defina o seu serviço
Crie um serviço simples com a lógica do seu negócio em um novo arquivo chamado “
services.py” dentro da pasta do seu aplicativo.Passo 2: Configurar o contêiner do injetor de dependências
Dentro do diretório do seu aplicativo, crie um contêiner usando a biblioteca
dependency-injectordentro de um novo arquivocontainers.py.Passo 3: Criar uma visualização com dependências injetadas
Use o decorador `
@inject` na sua visualização para receber dependências em `views.py`.Passo 4: Atualize o arquivo urls.py
Na pasta do seu projeto, vá até o arquivo
urls.pye conecte o seu injetor de dependências ao sistema de roteamento de URL do Django.Passo 5: Conecte o contêiner em apps.py
Vá até
apps.pye conecte o contêiner ao aplicativo.Depois, vá até a configuração do seu aplicativo em
INSTALLED_APPSe adicione seu aplicativo.Passo 6: Teste a visualização
Inicie o servidor Django e acesse a visualização através da URL
http://localhost:8000/greet/?name=Django.Você deve ver o seguinte.
Injeção de dependência no FastAPI
O FastAPI já vem com suporte para injeção de dependências através do marcador especial `
Depends()`, que injeta o que quer que uma função retorne.Aqui vai um guia passo a passo sobre como fazer a injeção de dependências no FastAPI.
Passo 1: Defina um serviço
Passo 2: Crie a função de dependência
Passo 3: Configure o aplicativo FastAPI e use a injeção de dependência
Passo 4: Executa e testa o aplicativo
Inicie o servidor usando o comando
uvicorn main:app –reloade acesse o URLhttp://localhost:8000/greet?name=FastAPI. Você deve ver a seguinte resposta.Comparando frameworks de injeção de dependências Python
Existem várias estruturas de dependência Python, cada uma com seus pontos fortes, sintaxe e casos de uso. Escolher a estrutura certa depende do tamanho e da organização do seu projeto.
Aqui tá uma comparação de algumas estruturas comuns de injeção de dependência Python.
Framework
Tudo de bom Para
Pontos fortes
Limitações
dependency-injectorAplicações complexas em uso na produção
Completo, rápido e fácil de configurar
Verbosidade
injectorSimples e ideal para aplicações mínimas
Sintaxe limpa
Menos avançado do que outros.
pinjectLógica de ligação avançada
Ligação automática
Mais lento por causa da introspecção
punqAplicações simples
Minimalista e rápido
Recursos limitados em comparação com outros
FastAPI
Depends()Aplicativos FastAPI
Suporte integrado para assíncrono e também testável
Não reutilizável fora do FastAPI
Flask-injectorAplicativos Flask
Fácil integração com aplicativos Flask
Depende de
injectordjango-injectorProjetos Django
Oferece injeção de dependência para visualizações e middleware.
Depende de
injectorPadrões avançados de implementação
À medida que o código da sua aplicação cresce, fica mais difícil manter a injeção de dependências.
Você pode usar a injeção de dependência avançada para lidar com tarefas como recursos no escopo da solicitação, limpeza e separação de interesses.
Dependências com escopo
As dependências com escopo permitem controlar por quanto tempo uma dependência fica ativa, por exemplo, quando você quer um novo objeto por solicitação, um objeto compartilhado durante toda a vida útil do aplicativo ou a limpeza automática de recursos, como conexões de banco de dados.
A maioria das estruturas de injeção de dependências suporta escopos como:
Aqui vai um exemplo de dependências com escopo usando contêineres aninhados com o `
dependency-injector`.No exemplo acima,
MyResourceimita uma classe que precisa de configuração e desativação, como abrir/fechar um arquivo ou banco de dados.O
containerfornece o recurso usandoproviders.Resourcee, quando você usawith container.resource(), ele entra e sai do contexto corretamente.Esse padrão garante que recursos, como identificadores de arquivos, conexões de banco de dados ou sessões de rede, sejam gerenciados de forma organizada.
Imagina um endpoint de API que precisa de uma sessão de banco de dados para a solicitação atual ou um logger com contexto específico da solicitação.
Com dependências com escopo, a estrutura cria um contêiner com escopo de solicitação quando a solicitação começa a resolver todas as dependências desse escopo.
Quando a solicitação termina, qualquer limpeza necessária, como fechar sessões do banco de dados, rola automaticamente. Isso garante que os estados não sejam compartilhados entre solicitações e evita vazamentos de recursos.
Injeção assíncrona
Para lidar com tarefas vinculadas a E/S de forma eficiente, você precisa de execução assíncrona. Os sistemas de injeção de dependência em frameworks como FastAPI e
dependency-injectoroferecem injeção assíncrona usandoasync/await.async defasync resourcesInjeção assíncrona é quando a gente injeta dependências que são funções assíncronas ou gerencia coisas assíncronas, tipo conexões de banco de dados assíncronas, APIs externas ou tarefas em segundo plano.Em vez de resolver tudo ao mesmo tempo, o sistema de injeção de dependências espera que tudo seja resolvido antes de fazer a injeção.
Aqui vai um exemplo de dependência assíncrona no FastAPI:
O FastAPI cuida automaticamente da resolução assíncrona de
get_usere injeta o resultado.dependency-injectortambém dá suporte ao gerenciamento assíncrono do ciclo de vida usando provedores de gerenciamento de recursos (AsyncResource).No código acima:
resources.AsyncResourceé usado pra lógica de configuração/desconfiguração assíncrona.await container.init_resources()inicializa todos os recursos e chamainit.await container.shutdown_resources()limpa todos os recursos.await container.db()pega a instância real dos recursos, que éAsyncDB.Usar injeção de dependência assíncrona ajuda a manter o desempenho da sua aplicação, evitando E/S síncronas em rotas assíncronas e eliminando a necessidade de aguardar manualmente as dependências na sua lógica.
Implicações de segurança
Embora a injeção de dependência melhore a modularidade e a testabilidade, ela pode trazer riscos de segurança se não for bem gerenciada.
Um risco importante é a confusão de dependências, em que pacotes maliciosos imitam os legítimos. Por exemplo, os invasores carregaram pacotes no PyPI com nomes usados em sistemas internos da empresa para enganar os aplicativos e fazê-los usar a versão maliciosa.
Para ficar seguro, nunca injete componentes não confiáveis ou controlados pelo usuário e valide as fontes de configuração, como variáveis de ambiente, antes de injetá-las.
Use os arquivos “
requirements.txt” ou “poetry.lock” pra bloquear versões exatas e automatizar a verificação de segurança em caso de alterações nas dependências. Além disso, certifique-se de verificar regularmente se tem vulnerabilidades nas suas dependências.Centralizar o controle das suas dependências também é essencial, pois garante a consistência entre as suas aplicações e facilita a aplicação de patches de segurança ou atualizações de versão. Isso ajuda a evitar “dependências ocultas” que podem aparecer de várias maneiras no código.
Tem várias ferramentas que podem detectar riscos e melhorar a sua segurança:
Aplicações no mundo real
A injeção de dependência não é só um padrão teórico; ela é usada em vários sistemas de produção pra gerenciar a complexidade, melhorar a flexibilidade e simplificar os testes.
Como configurar o serviço da Web
Nas aplicações web modernas, a injeção de dependências tem um papel super importante no gerenciamento de serviços padrão, como autenticação, registro em log e acesso a bancos de dados.
Estruturas como FastAPI são super importantes pra resolver rotas e dependências.
O exemplo abaixo mostra como é uma autenticação centralizada no FastAPI.
No código acima,
get_current_user()é injetado como uma dependência de rota, mas a lógica para verificar o token é centralizada e reutilizável.Você também pode fazer o mesmo para o acesso ao banco de dados, de modo que as sessões do banco de dados sejam gerenciadas e limpas automaticamente.
Aqui tá um exemplo de como você pode usar a injeção de dependência pra centralizar o acesso ao banco de dados.
No código acima, o
get_db_sessioné um provedor de dependências. Quando a rota `read_items` é chamada, o FastAPI automaticamente injeta o parâmetro `db`, permitindo que a rota acesse o banco de dados sem precisar gerenciar manualmente a configuração ou desativação da sessão.Desenvolvimento orientado por testes
A injeção de dependência facilita a alteração das dependências que os componentes recebem, o que é importante durante os testes.
Você pode trocar serviços reais por simulados durante os testes,deixando seu código mais modular e fácil de testar.
O FastAPI e o
dependency-injectoraceitam substituir dependências na hora do teste.Aqui tá um exemplo no FastAPI.
No exemplo de código acima,
get_db_sessioné substituído por um banco de dados simulado durante os testes; por isso, não é preciso corrigir ou alterar o código de produção.O mesmo também em
depedency-injector, você pode substituir seu banco de dados real por um banco de dados simulado para fins de teste, como no exemplo abaixo.O método `
override()` substitui a dependência real por uma simulação, que é limpa, controlada e reversível.Aqui estão algumas estratégias que você deve usar para substituir dependências:
FastAPI.dependency_overridesno FastAPI oudependency_injector.override()nodependency-injector.pytestpara criar simulações reutilizáveis e inseri-las por meio de fixtures.Melhores práticas e anti-padrões
Embora a injeção de dependência tenha como objetivo tornar o código modular e fácil de testar, ela pode causar bugs ou tornar o código mais complexo.
O que a gente recomenda
Aqui estão algumas dicas que você deve seguir ao criar dependências:
Erros comuns
Aqui estão algumas armadilhas comuns que você deve evitar ao usar injeção de dependência.
Teste de injeção de dependência em Python
A injeção de dependência facilita a inserção de objetos simulados nos seus componentes, simplificando assim os testes unitários. Isso permite separar os componentes básicos dos específicos do teste.
Com a injeção de dependência, os testes são determinísticos, não têm efeitos colaterais e resultam em uma execução mais rápida dos testes.
Aqui tá um exemplo de como você pode usar a injeção de dependência pra injetar versões simuladas, stubs ou falsas dessas dependências nos seus testes.
Nos testes, troca
EmailServicepor um mock:Certifique-se de usar injeção de dependência com frequência e evite monkey patching para facilitar a manutenção e evitar alterações significativas quando nomes internos forem alterados.
Conclusão
Embora você possa não precisar de injeção de dependência no começo para projetos pequenos, conforme o projeto cresce e se expande, você pode aproveitar a estrutura e a modularidade oferecidas pela injeção de dependência.
Olhando para o futuro, a injeção de dependência em Python parece promissora, e a gente pode esperar que ela continue com um sistema de tipos melhorado e um gerenciamento de ciclo de vida assíncrono aprimorado.
Dominar a injeção de dependência é essencial para se tornar um desenvolvedor Python fluente. Mas não para por aí; muitos outros conceitos avançados podem elevar suas habilidades em Python.
Para te ajudar a acelerar sua jornada de aprendizado, aqui estão alguns dos nossos recursos que você deve conferir.
Cursos:
Postagens no blog: