Pular para o conteúdo

A Regra da Dependência

Uncle Bob resume assim: source code dependencies must point only inward, toward higher-level policies. Quanto mais interno o círculo, mais alto o nível da política e mais estável ela deve ser. As Entities são o núcleo estável; Frameworks & Drivers são os detalhes voláteis.

A regra tem uma consequência absoluta: nada declarado em um anel externo pode ser mencionado por código de um anel interno — nem um nome de classe, nem uma função, nem uma variável, nem um formato de dado. Se o núcleo importa org.springframework.* ou jakarta.persistence.*, a regra já foi violada.

Porque as coisas que mudam por motivos diferentes e em ritmos diferentes devem ser separadas, e as políticas de negócio (que mudam devagar, por razões de negócio) não devem ser reféns de detalhes técnicos (que mudam rápido, por razões técnicas). Ao apontar as dependências para dentro, o banco de dados, o framework web e a UI tornam-se plugins substituíveis do núcleo.

O desafio prático: em runtime, o fluxo de controle frequentemente vai de fora para dentro (o controller chama o use case) e também de dentro para fora (o use case precisa persistir, chamar um gateway). O segundo caso pareceria forçar uma dependência de dentro para fora.

A solução é o DIP: o círculo interno define a interface que precisa, e o círculo externo a implementa. Assim a dependência de código-fonte se inverte em relação ao fluxo de controle.

classDiagram
    class GatewayPort {
        <<interface>>
        +salvar(pedido) void
    }
    class Interactor {
        -gateway: GatewayPort
    }
    class JpaGatewayAdapter {
        +salvar(pedido) void
    }
    Interactor ..> GatewayPort : usa (interno)
    JpaGatewayAdapter ..|> GatewayPort : implementa (externo)

O Interactor (interno) depende só da abstração GatewayPort, também interna. O JpaGatewayAdapter (externo) depende do núcleo, apontando para dentro. O fluxo de controle vai do interactor para a JPA, mas a dependência de código aponta ao contrário.

Cada vez que o controle cruza um dos círculos, ele passa por uma interface (port). Duas direções:

  • Input port (fronteira de entrada): a interface do caso de uso que o mundo externo invoca. O controller depende dela; o interactor a implementa.
  • Output port (fronteira de saída): a interface que o caso de uso invoca para entregar resultado ou pedir um serviço externo. O interactor depende dela; um presenter ou gateway a implementa.

Ver Use Cases para os detalhes dos interactors e presenters, e Ports para a origem hexagonal do conceito.

Objetos que atravessam a fronteira devem ser estruturas de dados simples e isoladas — request models e response models — nunca as Entities de domínio nem objetos do framework.

Regras práticas:

  • O que entra no use case é um request model (DTO de entrada), não o @RequestBody do controller diretamente.
  • O que sai é um response model (DTO de saída), não a Entity.
  • O DTO deve estar na forma mais conveniente ao círculo interno, jamais numa forma imposta pelo círculo externo (ex.: uma linha de ResultSet ou um objeto JPA não podem vazar para dentro).
  • Se você passa a Entity para fora, o mundo externo passa a depender do núcleo de forma acoplada — e mudanças no domínio quebram a UI. O DTO desacopla.
public record CriarPedidoRequest(String clienteId, List<ItemRequest> itens) {}
public record ItemRequest(String produtoId, int quantidade, BigDecimal precoUnitario) {}
public record PedidoResponse(String pedidoId, String status, BigDecimal total) {}

Esses records vivem no anel de Use Cases e são o contrato pelo qual controllers e presenters conversam com o núcleo sem tocar nas Entities.

  • Entity vazando para o controller: expor a Entity de domínio como resposta HTTP acopla a API ao domínio e viola a fronteira.
  • Import proibido no núcleo: qualquer import org.springframework ou jakarta.persistence dentro de Entities/Use Cases quebra a regra.
  • Interface no lugar errado: declarar o GatewayPort no pacote de infraestrutura em vez do núcleo. A interface pertence a quem a usa (o interactor), não a quem a implementa.
  • DTO anêmico que é a própria Entity: reaproveitar a Entity como DTO por preguiça, criando acoplamento reverso.
  • Dependência transitiva escondida: o núcleo não importa Spring diretamente, mas depende de uma lib que arrasta o framework junto.

1. Um Interactor pode importar uma classe do pacote de controllers?

Resposta

Não. O interactor está num círculo mais interno que os controllers (Interface Adapters). A dependência apontaria para fora, violando a regra. A comunicação ocorre via input/output ports definidos no núcleo.

2. Como a dependência de código pode apontar para dentro se o use case precisa chamar o banco?

Resposta

Via DIP: o use case depende de uma interface (output port/gateway) definida no próprio núcleo; a implementação JPA, na camada externa, implementa essa interface. O fluxo de controle vai para fora, mas a dependência de código-fonte aponta para dentro.

3. Por que não passar a Entity diretamente pela fronteira?

Resposta

Porque isso acopla o círculo externo ao formato interno. Qualquer mudança na Entity quebraria a UI e a API. Usa-se DTOs (request/response models) simples, na forma mais conveniente ao círculo interno, para desacoplar.

4. Onde deve ser declarada a interface do gateway?

Resposta

No círculo que a consome (o de Use Cases / núcleo), não no de infraestrutura. Quem usa a abstração é o dono dela; quem a implementa apenas a satisfaz — essa é a essência da inversão.