Skip to main content

Injeção de dependências em Python

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

feature python linting

31 de outubro de 2023

0 minutos de leitura

A injeção de dependências (DI) pode parecer complexa, mas, na prática, trata-se de deixar seu código mais flexível. É como dar um impulso ao software para torná-lo mais adaptável e robusto. E, como Python é uma linguagem de programação popular, entender a injeção de dependências é especialmente valioso. Imagine criar um software que não seja apenas bom, mas excelente — fácil de ajustar, expandir e proteger. Essa é a essência da DI em Python.

Se você está tentando descobrir como adicionar injeção de dependências ao seu trabalho, chegou ao lugar certo. Neste tutorial, você vai aprender como a DI funciona, como usá-la e como ela pode melhorar seus projetos em Python.

Como funciona a injeção de dependências

Para entender melhor como a DI funciona, pense na montagem de um carro. Não faria muito sentido se cada carro tivesse que fabricar os próprios pneus, certo? Em uma fábrica, os pneus são feitos separadamente e depois instalados no carro. Assim, se um carro precisar de pneus diferentes, é possível trocá-los. A DI usa essa mesma ideia no mundo do software.

Imagine que você está explorando uma aplicação Python. Dentro dela, há uma classe chamada DatabaseClient que precisa de informações específicas para se conectar a um banco de dados. Se você colocasse essas informações diretamente na classe, seria como ter um carro com pneus impossíveis de trocar. Com DI, em vez de deixar essas informações fixas, você as fornece (ou injeta) de fora. Assim, se mais adiante quiser alterar as informações ou experimentar outro banco de dados, poderá fazer isso sem desmontar toda a aplicação.

Essa abordagem oferece vários benefícios. É como ter uma caixa de ferramentas na qual você pode trocar facilmente as ferramentas de acordo com o trabalho. Seu software fica mais flexível: você pode experimentar e testar com facilidade, além de fazer atualizações ou correções sem complicação.

A seguir, confira alguns benefícios da injeção de dependências para entender melhor por que ela é importante.

Por que você precisa de injeção de dependências

A DI ajuda você a ter mais controle e facilita seu dia a dia na programação. Confira alguns dos principais motivos para considerar a adoção da DI nos seus projetos.

Melhora a manutenibilidade e a testabilidade do código

A injeção de dependências é um padrão que promove o desacoplamento dos componentes, reduzindo a dependência entre eles. Isso significa que, se um componente ou serviço precisar de uma atualização ou modificação, você poderá fazer essas alterações sem causar um efeito cascata em toda a base de código.

Além disso, a DI é uma ótima opção para testes. Quando os componentes estão desacoplados, fica muito mais simples injetar dependências simuladas durante os testes unitários. Assim, você pode testar o comportamento de cada parte de forma isolada, o que resulta em testes mais robustos e abrangentes.

Facilita a troca de dependências

Você já precisou encarar a tarefa desafiadora de trocar de tecnologia, como migrar de uma solução de banco de dados para outra? Com DI, transições como essa ficam bem menos complicadas. Em vez de refazer toda a classe ou o módulo que interage com o banco de dados, basta alterar a dependência injetada. Essa flexibilidade pode economizar muito tempo e reduzir o risco de erros durante migrações ou atualizações.

Aumenta a modularidade e a reutilização do código

A injeção de dependências incentiva a criação de componentes que não ficam estritamente vinculados a implementações específicas. Para você, isso significa mais modularidade na base de código. Módulos ou componentes desenvolvidos com DI em mente podem ser reutilizados facilmente em várias partes da aplicação ou até em projetos totalmente diferentes. Isso pode acelerar o desenvolvimento e promover uma abordagem de programação mais consistente.

Dá suporte à inversão de controle

Na programação tradicional, é comum que as classes criem e gerenciem as próprias dependências. Com DI, essa lógica é invertida. Em vez de a classe ser responsável por isso, entidades externas (geralmente um contêiner ou framework de DI) cuidam da criação e do fornecimento dessas dependências. Essa inversão de controle (IoC) pode simplificar o gerenciamento de dependências e promover uma arquitetura mais limpa e organizada.

Facilita a configuração do comportamento da aplicação

Uma das vantagens sutis, mas poderosas, da DI é a capacidade de alterar o comportamento da aplicação em tempo real. Ao injetar diferentes implementações de uma dependência, você pode modificar o funcionamento de determinadas partes da aplicação sem reescrever grandes quantidades de código. Seja para se adaptar a diferentes ambientes de execução ou atender às preferências de vários usuários, a DI oferece flexibilidade para personalizar o comportamento da aplicação.

Limitações da injeção de dependências em Python

Embora a DI ofereça muitos benefícios, é importante reconhecer que ela também traz desafios e limitações. Leve estes pontos em consideração ao decidir se vai incorporar DI aos seus projetos Python:

  • Complexidade: A DI muda fundamentalmente a forma como os componentes ou objetos são criados e gerenciados em uma aplicação. Em uma aplicação extensa e de grande escala, configurar a DI exige mapear cuidadosamente as dependências, as interfaces e as classes que as implementam. Essa camada adicional pode significar mais código repetitivo, mais configurações e uma curva de aprendizado mais íngreme, especialmente se você estiver adaptando um projeto existente para usar DI. Para fazer isso com sucesso, é preciso entender a fundo a arquitetura da aplicação.

  • Possibilidade de confusão: A DI pode ser abstrata. Se você ou seus colegas de equipe não conhecem bem a DI ou estão começando a usá-la, algumas partes da base de código podem parecer confusas. Em vez de criar objetos diretamente, eles são fornecidos ou injetados, o que pode dificultar inicialmente o rastreamento do fluxo da aplicação. Isso pode prolongar a integração de novos desenvolvedores ou tornar a curva de aprendizado mais íngreme.

  • Erros em tempo de execução: Os frameworks de DI geralmente conectam as dependências em tempo de execução. Se houver uma incompatibilidade ou faltar uma configuração, a aplicação poderá falhar ou se comportar de forma inesperada. Esses erros podem não aparecer durante a compilação ou nos testes iniciais, o que dificulta o diagnóstico e a correção. Testes adequados e conhecimento de DI ajudam a evitar esse problema, mas ele ainda é um risco possível.

  • Sobrecarga de desempenho: Embora a sobrecarga muitas vezes seja insignificante em muitas aplicações, resolver e injetar dependências em tempo de execução pode afetar o desempenho. Isso é especialmente relevante em situações nas quais cada milissegundo conta, como plataformas de negociação de alta frequência. É essencial fazer benchmarking e criar perfis da aplicação para entender essa sobrecarga e otimizar o que for necessário.

  • Desafios para reverter alterações: Como as dependências na DI estão fortemente conectadas, reverter uma alteração que cause problemas nem sempre é simples. As dependências podem gerar efeitos em cascata, ou seja, uma mudança em uma área pode afetar vários outros componentes. Por isso, testes completos, controle de versão e documentação cuidadosa se tornam ainda mais importantes.

  • Nem sempre é necessária: A DI é ideal para aplicações complexas, nas quais os componentes precisam ter baixo acoplamento, ser testados de forma isolada e oferecer mais modularidade. No entanto, em um script simples ou em uma aplicação pequena com poucos componentes, introduzir DI pode ser exagero. Isso pode aumentar a complexidade sem trazer benefícios concretos. Como acontece com qualquer padrão de projeto ou decisão arquitetural, é essencial avaliar se a DI é adequada para cada situação.

Injeção de dependências em frameworks Python populares

Nesta seção, você vai aprender a configurar a injeção de dependências usando três frameworks Python populares: Flask, Django e FastAPI. Cada framework tem sua própria abordagem, mas todos compartilham um princípio fundamental: desacoplar dependências para criar aplicações mais fáceis de manter, modulares e simples de testar.

Antes de conhecer os frameworks, confira se o Python está instalado. Se ainda não estiver, você pode baixar e instalar a linguagem pelo site oficial do Python.

Agora que você tem o Python na sua máquina, explore a injeção de dependências nesses diferentes frameworks.

Injeção de dependências no Flask

O Flask é um framework web leve. Embora não ofereça suporte integrado à DI, você pode integrar extensões como Flask-Injector para implementar esse recurso.

Para instalar o Flask e o Flask-Injector, use o seguinte comando:

pip3 install Flask Flask-Injector

O Flask-Injector integra perfeitamente a biblioteca Injector para permitir a injeção controlada de dependências em aplicações Flask.

Crie uma aplicação Flask

Para criar uma aplicação Flask simples, crie uma pasta chamada flask_di_project. Em seguida, nessa pasta, crie um arquivo chamado app.py e adicione o seguinte código:

from flask import Flask, jsonify
from flask_injector import FlaskInjector, inject, singleton

app = Flask(__name__)

class Database:
    def __init__(self):
        self.data = {"message": "Data from the Database!"}

class Logger:
    def log(self, message):
        print("Logging:", message)

class DataService:
    def __init__(self, database: Database, logger: Logger):
        self.database = database
        self.logger = logger

    def fetch_data(self):
        self.logger.log("Fetching data...")
        return self.database.data

@app.route('/')
@inject
def index(data_service: DataService):
    return jsonify(data_service.fetch_data())

# Dependency configurations
def configure(binder):
    binder.bind(Database, to=Database(), scope=singleton)
    binder.bind(Logger, to=Logger(), scope=singleton)
    binder.bind(DataService, to=DataService(Database(), Logger()), scope=singleton)

if __name__ == '__main__':
    FlaskInjector(app=app, modules=[configure])
    app.run(debug=True)

Neste código, você está criando uma aplicação web Flask que usa injeção de dependências para gerenciar seus componentes. A aplicação é formada por três classes: Database, Logger e DataService. Você define as dependências da classe DataService por meio da injeção pelo construtor, o que permite injetar instâncias de Database e Logger ao criar uma instância de DataService. Com o decorador @inject na função da rota index, você usa o Flask-Injector para injetar automaticamente uma instância de DataService na função. Além disso, uma função configure define as associações dessas dependências para que, sempre que uma instância for necessária, o Flask-Injector saiba como fornecê-la.

Execute e teste a aplicação

No terminal ou shell, acesse o diretório que contém o arquivo app.py e execute o seguinte:

python3 app.py

Abra um navegador e acesse http://127.0.0.1:5000/. Você deverá ver a mensagem {"message": "Data from the Database!"}:

Janela do navegador exibindo uma resposta JSON com a mensagem “Dados do banco de dados!”

Injeção de dependências no Django

O Django, um framework web de alto nível, oferece opções para usar DI por meio de bibliotecas externas. Neste exemplo, você vai usar a biblioteca Django Injector para adicionar DI ao seu projeto Django.

Use o seguinte comando para instalar o Django e o Django Injector:

pip3 install Django django-injector

Crie um projeto e uma aplicação Django

Para criar um novo projeto e uma aplicação Django, use os seguintes comandos:

django-admin startproject django_di_project
cd django_di_project
python3 manage.py startapp myapp

Depois de instalar o django-injector, você precisa adicioná-lo ao projeto. Acesse o arquivo settings.py, localizado na pasta django_di_project, e adicione django_injector à lista INSTALLED_APPS:

INSTALLED_APPS = [
    … code omitted …
    'django_injector',
]

Em seguida, em myapp/views.py, use django-injector para implementar a DI:

from django.http import JsonResponse
from injector import inject
from django.views import View
from myapp.services import DataService  # Import your DataService class

class MyView(View):
    @inject
    def __init__(self, data_service: DataService):
        self.data_service = data_service
        super().__init__()

    def get(self, request):
        data = self.data_service.fetch_data()
        return JsonResponse(data)

Aqui, você definiu uma view do Django, MyView, que, quando acessada por uma requisição GET, busca dados usando o DataService com dependências injetadas e os retorna como uma resposta JSON.

Atualize as URLs

Atualize django_di_project/urls.py para direcionar a URL raiz para essa view:

from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('', MyView.as_view(), name='index'),
]

Em seguida, crie um arquivo services.py no diretório myapp e defina nele a classe DataService:

from injector import inject

class DataService:
    @inject
    def __init__(self):
        self.message = "Hello from Dynamic DataService in Django!"

    def fetch_data(self):
        return {"message": self.message}

Ao incorporar django-injector, você integrou perfeitamente a DI à sua aplicação Django. O decorador @inject injeta com eficiência a instância de DataService no construtor da classe MyView, promovendo a modularidade e facilitando o gerenciamento da aplicação Django.

Execute e teste a aplicação

No terminal ou shell, dentro do diretório django_di_project, execute o seguinte:

python3 manage.py runserver

Abra o navegador e acesse http://127.0.0.1:8000/. Você deverá ver a mensagem {"message": "Hello from Dynamic DataService in Django!"}:

Navegador exibindo uma resposta JSON: “Olá do Dynamic DataService no Django!” em localhost:8000

Injeção de dependências no FastAPI

O FastAPI é um framework web moderno que oferece suporte nativo à DI. Com o FastAPI, você conta com um framework que integra a DI de forma inerente, permitindo criar aplicações modulares e fáceis de testar sem depender de bibliotecas externas.

Use o seguinte código para instalar o FastAPI e o Uvicorn (um servidor ASGI para executar o FastAPI):

pip3 install fastapi uvicorn

Crie uma aplicação FastAPI simples

Para criar uma aplicação FastAPI simples, crie uma nova pasta chamada fast_di_project no diretório raiz. Dentro dessa pasta, crie um arquivo chamado main.py e adicione o seguinte código:

from fastapi import FastAPI, Depends

app = FastAPI()

class DataService:
    def fetch_data(self):
        return {"message": "Greetings from the Dynamic DataService in the World of FastAPI!"}

def get_data_service():
    return DataService()

@app.get('/')
def index(data_service: DataService = Depends(get_data_service)):
    data = data_service.fetch_data()
    return {"result": data, "info": "Fetched from the Dynamic DataService in FastAPI!"}

Aqui, você começa criando uma instância da classe FastAPI. Em seguida, define uma classe DataService que encapsula um método para buscar dados. Ao acessar a URL raiz da aplicação, o gerenciamento de dependências integrado do FastAPI entra em ação. Você criou a função get_data_service() para fornecer uma instância da classe DataService. O mecanismo Depends do FastAPI permite injetar essa instância na função de rota index. Ao acessar a URL raiz no navegador, você receberá uma resposta com os dados buscados e uma mensagem personalizada do DataService dinâmico no universo do FastAPI.

Execute e teste a aplicação

No terminal ou shell, acesse o diretório que contém main.py e execute o seguinte:

uvicorn main:app --reload

Abra o navegador e acesse http://127.0.0.1:8000/. Você deverá ver a mensagem {"result":{"message":"Greetings from the Dynamic DataService in the World of FastAPI!"},"info":"Fetched from the Dynamic DataService in FastAPI!"}:

Navegador exibindo uma resposta local do FastAPI com saudações em JSON do Dynamic DataService.

Todo o código usado neste tutorial está disponível neste repositório do GitHub.

Frameworks de injeção de dependências para Python

Na hora de escolher um framework de DI, é importante avaliar os requisitos específicos do seu projeto, o nível de modularidade e testabilidade desejado e o framework com o qual você está trabalhando. Cada opção oferece recursos diferentes, e sua escolha deve estar alinhada à complexidade e aos objetivos de design do projeto. Confira algumas opções:

Flask-Injector

O Flask-Injector é uma excelente opção para criar aplicações Flask. Ele é especialmente útil em projetos leves baseados no framework Flask.

O Flask-Injector se integra perfeitamente ao ecossistema Flask, facilitando o gerenciamento de dependências e a modularidade da sua aplicação. No entanto, para aplicações maiores e mais complexas, que talvez precisem de recursos avançados de gerenciamento de dependências, vale considerar outros frameworks de DI com funcionalidades adicionais voltadas a essas necessidades específicas.

Django Injector

Se você desenvolve com Django, a biblioteca Django Injector pode ser muito útil. Ela simplifica a DI em projetos Django e é uma excelente opção para alcançar modularidade e testabilidade.

Considere usar o Django Injector ao criar aplicações Django que exigem integração simples de DI, especialmente em visualizações baseadas em classes, middleware e comandos de gerenciamento.

Depends

A função Depends do FastAPI foi projetada para funcionar com o framework asyncio do Python e oferece recursos de DI para código assíncrono. Se você está criando aplicações assíncronas com FastAPI, Depends pode ajudar a gerenciar e injetar dependências em funções assíncronas e corrotinas. Esse recurso é especialmente útil em projetos assíncronos no FastAPI, contribuindo para manter a qualidade do código e um design modular.

Dependency Injector

A biblioteca Dependency Injector é um framework de DI abrangente, adequado para diversas aplicações Python. Ela oferece recursos avançados, como gerenciamento de contêineres, escopo e controle do ciclo de vida.

Use o Dependency Injector em projetos maiores que exigem um gerenciamento robusto de dependências, inversão de controle e cenários complexos nos quais você precisa ter mais controle sobre como as dependências são injetadas e gerenciadas.

Injector

A biblioteca Injector é um framework de DI de uso geral que pode ser integrado a diferentes projetos Python, independentemente do framework. Ela é especialmente indicada para projetos em que você quer configurar e gerenciar manualmente a injeção de dependências, sem integrações específicas de frameworks.

Use o Injector quando precisar de uma solução de DI flexível e versátil para aplicações que abrangem vários frameworks ou até mesmo scripts independentes.

Mantenha as dependências do seu projeto seguras com a Snyk

Quando o foco é a funcionalidade, não dá para deixar a segurança de lado. É aí que a Snyk pode ajudar. Com o kit de ferramentas da Snyk, você pode:

  • Analisar dependências Python, identificar vulnerabilidades e receber sugestões de correção.

  • Receber feedback em tempo real sobre problemas de segurança enquanto programa.

  • Contar com soluções automatizadas para as vulnerabilidades detectadas.

  • Integrar-se facilmente aos pipelines de integração e entrega contínuas (CI/CD) mais usados, garantindo verificações de segurança consistentes.

Ao incorporar as extensões de IDE da Snyk ao seu fluxo de trabalho, você adota proativamente práticas de programação segura desde o início e simplifica o gerenciamento de dependências. Para saber mais sobre a Snyk, consulte a documentação oficial da Snyk.

Conclusão

Neste artigo, você aprendeu por que a injeção de dependências é necessária em Python. Também conheceu alguns dos benefícios, como a melhoria da manutenção e da modularidade do código. Além disso, viu alguns dos desafios, incluindo o possível aumento da complexidade e o risco de erros em tempo de execução. Por fim, aprendeu a integrar a injeção de dependências a frameworks Python conhecidos, como Flask, FastAPI e Django.

Embora a DI possa trazer alguns desafios, dominar suas particularidades pode elevar a qualidade das aplicações Python. Ao lidar com o gerenciamento de dependências, é essencial usar ferramentas como a Snyk para manter a segurança em primeiro plano durante o desenvolvimento. Para saber mais sobre gerenciamento de dependências e boas práticas em Python, confira no blog da Snyk o gerenciador de pacotes Python Poetry. Esse recurso apresenta uma visão abrangente de ferramentas que podem ajudar você a gerenciar seus projetos Python com mais eficiência.

Comece a resolver desafios de capture the flag

Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.