Você já escreveu algo assim numa aplicação FastAPI?
| |
O código funciona. Mas há um problema sério: cada endpoint é responsável por montar sua própria árvore de dependências. Quando OrderService precisar de um CacheClient e de um EventPublisher, quem vai sofrer é quem escreve — e depois testa — cada endpoint.
O FastAPI tem seu próprio sistema de Depends() que resolve parte disso, mas tem limites quando a aplicação cresce e os grafos de dependência ficam complexos. É aqui que entra o conceito de Inversão de Controle e, mais especificamente, uma biblioteca que acerta onde o Depends() tropeça: o Dishka.
O que é IoC, de verdade
Inversão de Controle (IoC) é o princípio de que um módulo não deve instanciar suas próprias dependências — ele deve recebê-las de fora. A confusão comum é que IoC e injeção de dependência (DI) são a mesma coisa. Não são.
IoC é o princípio. DI é um padrão que implementa esse princípio. Um container de IoC é a infraestrutura que automatiza o processo de construção e entrega dessas dependências.
No ecossistema Python, o padrão mais comum é passar dependências via construtor:
| |
O OrderService não sabe como criar um OrderRepository. Ele apenas declara que precisa de um. Quem resolve isso é o container de IoC — e é exatamente o que o Dishka faz.
O limite do Depends() nativo
O sistema de DI do FastAPI resolve dependências por requisição, o que é ótimo para muitos casos:
| |
O problema aparece quando você tem:
- Dependências com ciclos de vida diferentes — uma conexão de banco vive por requisição, mas um
HTTPClientpode viver pela duração do processo. - Grafos profundos — quando
ServiceAdepende deServiceBque depende deRepositoryAeRepositoryB, e cada um tem suas próprias dependências, o arquivo de “providers” vira um espaguete deDepends. - Reutilização fora do contexto HTTP — se você precisar do mesmo serviço num worker Celery, num script de CLI ou num teste, não há como reutilizar a cadeia de
Depends()sem acoplamento desnecessário ao FastAPI. - Testabilidade — substituir dependências em testes com
app.dependency_overridesfunciona, mas escala mal.
Dishka: o container de IoC que entende escopo
O Dishka é um container de DI para Python que resolve exatamente esses problemas. Ele foi desenhado com escopo em mente desde o início.
Instale:
| |
Para integrar com FastAPI:
| |
Conceitos centrais
Provider: uma classe que declara como construir dependências.
Scope: define o ciclo de vida de uma dependência. Os escopos principais são:
Scope.APP— criado uma vez, vive durante toda a aplicaçãoScope.REQUEST— criado por requisição HTTP e destruído no finalScope.SESSION— útil para workers ou tarefas com vida intermediária
Container: o objeto central que resolve dependências com base nos providers registrados.
Construindo do zero: uma API de pedidos
Vamos construir uma API de pedidos com PostgreSQL, Redis para cache e publicação de eventos. A estrutura de diretórios:
app/
├── main.py
├── providers.py
├── routers/
│ └── orders.py
└── services/
├── order_service.py
├── order_repository.py
└── cache_client.py
Definindo os serviços
| |
| |
| |
Nenhum desses serviços sabe como criar suas próprias dependências. Perfeito.
Definindo os providers
Aqui é onde o Dishka entra de fato. Um provider declara como construir cada dependência e qual é o seu escopo:
| |
Dois detalhes importantes aqui:
Primeiro, o @provide(scope=Scope.APP) com yield é um gerador que funciona exatamente como um context manager — o código após o yield é o cleanup. O engine é criado uma vez na inicialização e destruído no shutdown. Isso é análogo ao lifespan do FastAPI, mas declarado dentro do provider.
Segundo, o ServiceProvider define scope = Scope.REQUEST como padrão de classe e aplica a todos os @provide sem escopo explícito. Isso evita repetição.
Montando a aplicação
| |
Usando nos endpoints
A integração com FastAPI injeta o container no request e resolve as dependências automaticamente. Nos endpoints, você usa FromDishka para declarar o que precisa:
| |
O endpoint não sabe nada sobre banco de dados, Redis, ou como montar um OrderService. Ele declara o que precisa e recebe. O container resolve o grafo inteiro.
Escopo na prática: o que acontece em cada requisição
Quando uma requisição chega em GET /orders/42:
- O Dishka abre um escopo de
REQUEST - Resolve
OrderService— que precisa deOrderRepositoryeCacheClient OrderRepositoryprecisa deAsyncSession— criada dentro do escopo de REQUEST, com transação abertaCacheClientprecisa deredis.Redis— reutiliza a conexão de escopo APP, cria apenas o wrapperOrderServiceé construído com as dependências resolvidas- O endpoint executa
- Ao final da requisição, o escopo de REQUEST é encerrado — a transação é commitada (ou revertida em caso de exceção) e a sessão é fechada
O Redis e o engine do SQLAlchemy não são recriados. Eles vivem no escopo APP e são compartilhados entre requisições. A sessão e o cliente de cache recebem uma nova instância por requisição.
Testabilidade: o ganho real
Com o Dishka, substituir dependências em testes é declarativo e não polui o código de produção:
| |
Nenhum banco. Nenhum Redis. O container de teste substitui apenas o que interessa — o serviço — e o resto do pipeline HTTP (serialização, validação de schema, roteamento) funciona normalmente.
Compare isso com app.dependency_overrides: aqui você substitui providers inteiros de forma composível, sem precisar conhecer a cadeia de Depends que está por baixo.
Reutilizando fora do FastAPI
Essa é uma vantagem que o Depends() nativo simplesmente não oferece. Precisa processar pedidos num worker?
| |
O mesmo container, os mesmos providers, os mesmos serviços — sem nenhum acoplamento ao FastAPI. A configuração de escopos funciona da mesma forma.
Quando o Depends() ainda é a escolha certa
Dishka não é a resposta para tudo. O sistema nativo do FastAPI é mais simples de entender, tem zero dependências extras e funciona perfeitamente para:
- APIs pequenas onde o grafo de dependências é raso
- Dependências que são genuinamente específicas do contexto HTTP (request body, headers, path params)
- Projetos onde a equipe já está familiarizada com o
Depends()e a complexidade não justifica a troca
O Dishka brilha quando a aplicação cresce, quando você precisa de múltiplos escopos de ciclo de vida, quando os serviços precisam ser compartilhados com workers ou scripts, e quando a testabilidade começa a sofrer com dependency_overrides espalhados.
Saindo da teoria e indo para o que importa: se você tem uma API FastAPI que está crescendo e os Depends() começaram a se multiplicar de forma difícil de rastrear, o Dishka resolve isso com uma abordagem que você vai reconhecer se já usou Spring, .NET DI ou qualquer container IoC mais robusto. A curva de aprendizado é pequena e o ganho em testabilidade e organização é imediato.
Se você implementou algo parecido ou tem dúvidas sobre como organizar os providers em projetos maiores, continua o papo no Fediverse: @riverfount@bolha.us.
