Pular para o conteúdo

Ports

Uma porta é um contrato — uma interface — que pertence ao interior do hexágono e é expresso inteiramente na linguagem do domínio. Ela descreve o quê acontece, nunca como. Nenhuma porta deve mencionar HTTP, SQL, JSON, Kafka ou qualquer detalhe de framework; se mencionar, ela vazou tecnologia para dentro do núcleo.

A regra prática: quem define a interface é o núcleo, quem a implementa (ou consome) é a periferia. Isso mantém o fluxo de dependência sempre apontando para dentro.

São a API do núcleo: representam os casos de uso que a aplicação oferece. São implementadas pelo núcleo (por um application service) e chamadas pelos adapters primários (um controller REST, uma CLI, um teste).

  • Direção da chamada: adapter primário → driving port.
  • Direção da dependência: adapter primário → núcleo (aponta para dentro).
  • Exemplo: AbrirContaUseCase, TransferirUseCase.
package com.exemplo.banco.application.port.in;
import com.exemplo.banco.domain.Conta;
import java.math.BigDecimal;
public interface AbrirContaUseCase {
Conta abrir(String titular, BigDecimal depositoInicial);
}

Driven ports (portas secundárias / de saída, SPIs)

Seção intitulada “Driven ports (portas secundárias / de saída, SPIs)”

São SPIs (Service Provider Interfaces): o que o núcleo precisa que alguém forneça. São definidas pelo núcleo mas implementadas pelos adapters secundários (JPA, cliente HTTP, produtor de fila). É aqui que a inversão de dependência fica mais evidente: o repositório é uma interface do domínio, e a persistência a implementa.

  • Direção da chamada: núcleo → driven port.
  • Direção da dependência: adapter secundário → núcleo (a implementação aponta para dentro).
  • Exemplo: ContaRepository, NotificacaoGateway.
package com.exemplo.banco.application.port.out;
import com.exemplo.banco.domain.Conta;
import java.util.Optional;
public interface ContaRepository {
Conta salvar(Conta conta);
Optional<Conta> porNumero(String numero);
}

Ambos os tipos de porta são a materialização do DIP - Dependency Inversion Principle: módulos de alto nível não dependem de módulos de baixo nível; ambos dependem de abstrações. No lado driven a inversão é literal — sem a interface, o application service (alto nível) dependeria do JPA (baixo nível). Com a driven port, ambos dependem da abstração declarada no núcleo. As portas são, portanto, a espinha dorsal que faz o hexágono funcionar; sem elas, restaria acoplamento direto. Veja também SOLID.

classDiagram
    class AbrirContaUseCase {
        <<driving port>>
        +abrir(titular, deposito) Conta
    }
    class ContaService {
        <<application service>>
        +abrir(titular, deposito) Conta
    }
    class ContaRepository {
        <<driven port>>
        +salvar(conta) Conta
        +porNumero(numero) Optional~Conta~
    }
    class ContaRestController {
        <<adapter primario>>
    }
    class ContaJpaAdapter {
        <<adapter secundario>>
    }

    ContaRestController ..> AbrirContaUseCase : chama
    ContaService ..|> AbrirContaUseCase : implementa
    ContaService ..> ContaRepository : usa
    ContaJpaAdapter ..|> ContaRepository : implementa
  • Porta que expõe tipos de framework: métodos que recebem/retornam Entity JPA, ResponseEntity, Page do Spring. A porta deve falar só a língua do domínio.
  • Driving port igual à assinatura do controller: copiar DTOs de request/response para dentro da porta; a porta usa comandos e tipos do domínio, não o payload HTTP.
  • Repositório genérico onipotente: uma driven port que expõe EntityManager ou métodos CRUD infinitos vaza a tecnologia e amarra o núcleo à persistência.
  • Interface por vaidade: criar uma porta trivial de um método só para “ter interface”, sem múltiplos consumidores nem intenção de troca — cerimônia sem valor.
  • Confundir os lados: implementar a driving port em um adapter, ou fazer o núcleo implementar a driven port. A driving é implementada dentro; a driven, fora.

1. Qual a diferença essencial entre uma driving port e uma driven port?

Resposta

A driving port é a API do núcleo (caso de uso), implementada dentro do hexágono e chamada de fora. A driven port é uma SPI, definida no núcleo mas implementada fora, e chamada de dentro para fora. Uma é “o que a aplicação faz”; a outra é “o que a aplicação precisa”.

2. Por que um repositório é uma porta e não um detalhe de infraestrutura?

Resposta

Porque a interface do repositório pertence ao núcleo e é expressa na linguagem do domínio (porNumero, salvar(Conta)). A tecnologia de persistência (JPA, Mongo) é apenas o adapter que a implementa. Assim, o domínio depende da abstração, não do banco — exatamente o DIP.

3. Como uma porta pode “vazar” tecnologia e por que isso é grave?

Resposta

Quando seus métodos usam tipos de framework (ResponseEntity, @Entity, Page) ou conceitos externos (SQL, tópicos Kafka). É grave porque reintroduz a dependência do domínio na tecnologia — o exato problema que a arquitetura veio resolver — impedindo troca de adapter e teste isolado.

4. Em que sentido as portas são a materialização do DIP?

Resposta

Elas são as abstrações das quais tanto o módulo de alto nível (application service) quanto o de baixo nível (JPA, HTTP) dependem. Sem a porta, o alto nível dependeria diretamente do baixo nível; com ela, a dependência da implementação aponta para dentro, para o contrato do domínio.