No artigo anterior, resumimos modularização como “agrupar o que muda junto para estabelecer limites”.
Mas o problema real começa depois de dividir os módulos. As partes passam a referenciar umas às outras.
A usa B, B usa C e, um dia, C volta a usar A. Os benefícios desaparecem porque os três viram, na prática, uma única unidade.
A resposta curta: há dois princípios. Mantenha o fluxo de dependências em uma direção e faça o que muda com frequência depender do que é estável.
Este artigo resume como definir a direção, os caminhos típicos dos ciclos e três maneiras de quebrá-los.
Em que direção as dependências devem fluir?
Dependências têm direção. Se o módulo A importa o módulo B, A depende de B.
Existe uma resposta clássica para a direção da seta.
O instável deve depender do estável. Quando isso se inverte, uma pequena mudança se espalha pelo sistema inteiro.
Um módulo estável muda pouco, como modelos de domínio e protocolos compartilhados.
Um módulo instável muda com frequência: a UI e a lógica de resposta a eventos ajustada continuamente aos requisitos.
Por isso, um grafo de dependências saudável geralmente segue esta direção.
- Módulos de tela e funcionalidade → módulos de domínio → módulos de interfaces compartilhadas
- As setas fluem apenas para baixo, em direção ao estável; nunca sobem.
Robert C. Martin chamou isso de Princípio das Dependências Estáveis (SDP, Stable Dependencies Principle).
Por que surgem dependências circulares?
Na maioria das vezes, ciclos surgem naturalmente, sem má intenção. Um caminho típico é este.
O módulo Order referencia o módulo User porque um pedido precisa dos dados de quem o fez.
Com o tempo surge o requisito de mostrar “pedidos recentes” na tela do usuário. Quando User referencia Order, o ciclo se fecha.
Quando surge um ciclo, três coisas entram em colapso.
- Falha na separação das unidades de build: o compilador não consegue processá-las separadamente e elas se tornam, na prática, um módulo (Swift bloqueia imports circulares entre módulos com erro de compilação).
- Testes independentes ficam impossíveis: testar A exige B, e testar B exige A — um deadlock.
- O impacto se torna imprevisível: qualquer correção exige revisar também o outro lado.
Como quebrar dependências circulares?
Na prática, há três métodos principais.
Primeiro, mova as partes comuns para baixo.
Se os dois lados realmente precisam de “uma parte do outro”, extraia essa parte para um módulo inferior mais estável. Por exemplo, mova para um módulo de modelos de domínio apenas os tipos usados por Order e User.
Segundo, inverta a direção usando interfaces (DIP).
Substitua uma dependência por um protocolo. User define apenas o protocolo “algo que fornece uma lista de pedidos”, e Order fornece a implementação.
// User Módulo: declare apenas as capacidades necessárias como protocolo
public protocol OrderHistoryProviding {
func recentOrders(of userID: String) -> [OrderSummary]
}
// Order Módulo: User Adote e implemente o protocolo do módulo
public struct OrderHistoryProvider: OrderHistoryProviding {
public func recentOrders(of userID: String) -> [OrderSummary] {
// Consultar o repositório de pedidos
}
}
Assim, resta apenas uma seta: Order → User. A inversão de dependência do artigo anterior sobre DIP é aplicada diretamente no nível dos módulos.
Terceiro, mova a montagem para cima.
Em vez de conectar os módulos diretamente, um módulo superior que conhece ambos — o app target ou a camada de montagem — faz a conexão. É especialmente útil quando módulos de funcionalidade precisam chamar uns aos outros para navegação.
| Situação | Como quebrar |
|---|---|
| Cada lado precisa apenas de parte dos tipos do outro | Extrair os tipos comuns para um módulo inferior |
| Um lado chama a funcionalidade do outro | Definir um protocolo e inverter a dependência |
| Navegação entre módulos de funcionalidade | Conectar na camada superior de montagem |
Quando aplicar e quando é exagero?
Gerenciar a direção das dependências também tem custo: surgem mais protocolos e código de montagem.
- Com 3 ou 4 módulos ou mais e equipes separadas, vale documentar as regras e monitorar ciclos com ferramentas.
- Em um projeto pequeno com dois módulos, basta evitar ciclos. Envolver toda referência em um protocolo é exagero.
- Em um legado que já tem ciclos, não tente quebrar tudo de uma vez; comece pelo módulo que muda com mais frequência.
É assim que perguntam em entrevistas
P. Por que dependências circulares entre módulos são um problema e como você as resolveria?
Um ciclo transforma dois módulos em uma unidade para build, teste e deploy, eliminando os benefícios da modularização. Extraia tipos comuns para um módulo inferior, inverta a direção com um protocolo (DIP) ou conecte os dois em uma camada superior de montagem para deixar as setas em uma única direção.
P. Explique o Princípio das Dependências Estáveis (SDP).
Um módulo deve depender apenas de módulos mais estáveis que ele, ou seja, que mudem menos. Se um módulo que muda frequentemente ficar a jusante, suas mudanças se propagam a montante; por isso, módulos de domínio e interface com baixa frequência de mudança devem ficar na parte inferior do grafo.
No próximo artigo, veremos uma armadilha comum na modularização: o módulo Common, perigoso já no nome.
Vamos mostrar como “é compartilhado, então vamos colocar no Common” transforma todo o sistema novamente em uma unidade e como evitar isso com uma estrutura em camadas.

![Imagem de capa de [Modularização #2] Dependências entre módulos: como projetar e quebrar ciclos](/assets/images/posts/43006932-2be1-4618-af56-bd9ceee6264e/1.jpg)