Aula 02 · 55–70 minutos
O contrato pertence ao consumidor
Segunda aula: desenhe uma interface pequena no pacote consumidor em Go.
Resultado de aprendizagem e roteiro
Ao final desta aula, você poderá olhar para um serviço Go, descobrir a colaboração mínima que ele consome e justificar por que o contrato deve morar nesse pacote. Você também saberá separar o que o compilador prova do que um teste unitário e um teste de integração precisam provar.
- Partimos de uma regra de checkout e observamos o acoplamento.
- Desenhamos o contrato a partir de verbos usados, não da API do banco.
- Seguimos a execução em produção e no teste.
- Praticamos uma decisão, verificamos seu efeito e a defendemos em entrevista.
Primeiro, siga o fluxo da aplicação
Em uma compra, o código de checkout valida o pedido e precisa registrá-lo. Essa é a única informação que importa para a regra: "consigo salvar este pedido?". O banco, o driver e o SQL ficam fora desse fluxo central.
pedido → checkout.Service → OrderSaver.Save → adaptador
regra contrato postgres ou fakeO contrato fica no pacote checkout porque é ele que conhece a necessidade. O adaptador PostgreSQL muda para atender a esse contrato, em vez de o negócio moldar-se à API do banco.
Quando o código executa — e por que o acoplamento dói
- Compilação: Go aceita qualquer tipo que tenha o método exigido por
OrderSaver. - Inicialização:
mainescolhe se conectará PostgreSQL ou outra implementação. - Execução da regra:
ServicechamaSave, sem saber como a persistência acontece. - Teste: um fake em memória recebe a chamada e permite observar o efeito da regra.
O problema existe quando o serviço depende de um repositório concreto: mudanças no detalhe atravessam a regra e o teste precisa carregar infraestrutura que não é parte da decisão que queremos verificar.
A solução: descreva somente a colaboração necessária
Comece pelo verbo que a regra precisa. Neste caso, salvar um pedido. A interface fica junto de seu consumidor e não promete consultas, transações ou métodos que o serviço não usa.
// package checkout
type OrderSaver interface {
Save(ctx context.Context, order Order) error
}
type Service struct {
orders OrderSaver
}
Não há palavra especial de "implementação" em Go. Se PostgresOrderRepository tem esse método, ele satisfaz a interface. Se amanhã a regra também precisar buscar um pedido, crie ou amplie um contrato apenas quando essa nova necessidade for real.
Abrir o fluxo em tela inteira. Leia da necessidade de negócio até os dois adaptadores.
Recupere da memória
Qual pacote deve declarar OrderSaver quando somente ele precisa salvar pedidos?
Fonte primária recomendada
Go Code Review Comments — Interfaces. A orientação é criar interfaces no lado que as usa e evitar defini-las antes de haver uma necessidade concreta.
A seguir: Aula 03 — Compor na borda.
Da necessidade ao contrato: uma decisão verificável
Problema em trabalho real: Uma regra que depende de uma API de repositório inteira passa a carregar decisões que não toma. Por exemplo, checkout não precisa saber como pesquisar ou apagar pedidos para decidir registrá-los. Esse acoplamento faz uma alteração do adaptador aparecer como alteração da regra.
Fluxo e momento de execução: Em compilação, Go aceita o valor entregue ao construtor somente se seu conjunto de métodos satisfaz OrderSaver. Na inicialização, main escolhe o repositório. Durante Place, Service usa somente Save. No teste, o mesmo ponto recebe um fake. A chamada é igual; o valor dinâmico muda.
Abordagem insuficiente: Evite começar pelo repositório e publicar um contrato com todas as suas capacidades. Ele faz o consumidor prometer métodos sem necessidade e transforma detalhes de persistência em API.
Decisão recomendada: Comece pelo verbo que aparece na regra. Nomeie o contrato pelo comportamento necessário e mantenha os tipos de domínio no lado do consumidor. Uma nova necessidade pode receber outro contrato, em vez de inflar este.
Como verificar na prática: Faça o pacote checkout compilar sem importar postgres; em seguida, passe o repositório existente a NewService e rode go test ./.... O compilador prova a forma do contrato; um teste de integração separado ainda deve provar SQL, conexão e migrações.
Limite de segurança: Contexto e erro atravessam a fronteira para permitir cancelamento e tratamento correto. O contrato não substitui validação de pedido, autorização nem consultas parametrizadas: cada controle permanece na borda responsável.
Fonte primária: Go Code Review Comments — Interfaces.
Guia de solução do exercício
Fluxo de execução: A regra revela o verbo necessário; esse verbo vira interface; adaptadores de produção e teste atendem ao mesmo contrato.
Abordagem inadequada: Definir uma interface ampla no pacote postgres faz a infraestrutura decidir a forma da regra.
// package postgres
type Repository interface { Save(Order) error; Find(string) (Order, error); Delete(string) error }
Por que falha e como corrigir: O contrato nasce com operações que checkout não usa. Isso cria acoplamento acidental e torna mudanças no adaptador mudanças na API do negócio.
Abordagem correta: Coloque a menor interface no pacote consumidor e deixe os tipos concretos implementarem-na implicitamente.
// package checkout
type OrderSaver interface { Save(Order) error }
Raciocínio, trade-offs e limites: A compilação verifica o conjunto de métodos quando o repositório é passado ao construtor. No teste, um fake também fornece Save. Só amplie a interface quando uma nova regra consumir outro comportamento; caso contrário, mantenha o contrato pequeno.
Abra o diagrama relacionado e compare o fluxo.
Pergunta de mercado e entrevista
1. Onde você declararia uma interface em Go?
Como responder: No pacote que consome o comportamento, depois de identificar uma necessidade real. Mostre OrderSaver e a implementação implícita.
2. Por que a interface deve ser pequena?
Como responder: Ela expressa a necessidade do consumidor, não a capacidade completa do adaptador. Métodos não usados aumentam acoplamento e tornam o contrato mais caro de manter.
3. Quando ampliar uma interface?
Como responder: Somente quando o mesmo consumidor passa a precisar de outro comportamento. Se outro caso de uso precisa de outro conjunto de métodos, prefira outro contrato pequeno.
Pratique mais 9 questões de recuperação
Responda sem consultar a aula. O feedback é imediato; volte ao trecho relevante antes de tentar de novo.
DI entrega uma dependência; DIP define:
Em Go, uma interface é satisfeita quando:
Quem deve possuir uma interface pequena?
Onde a implementação concreta é escolhida?
Para testar uma regra sem banco, injete:
Uma interface deve conter:
O detalhe PostgreSQL deve depender de:
Qual sinal pede uma interface?
Após a refatoração, a regra não deve importar: