Arquitetura em Go: Interfaces Pequenas, Dependências Explícitas
Go não envia herança, anotações ou um contêiner de injeção de dependência em tempo de execução.
Busque em todas as páginas da documentação
Go não envia herança, anotações ou um contêiner de injeção de dependência em tempo de execução.
Essas ausências são recursos: elas levam as equipes a adotar interfaces pequenas, conexão de construtores e limites de pacotes que compilam de forma limpa e permanecem testáveis à medida que os serviços crescem.
internal/, composição, funções construtoras, superfície da API do pacote.init().main; nenhuma conexão mágica cross-cutting; a disciplina de arquitetura é imposta por convenção e revisão, não apenas pelo compilador.Pacotes Go são a unidade primária de reutilização.
Um pacote exporta um conjunto de identificadores; todo o resto permanece privado.
Não há subclassing: o comportamento é compartilhado através de composição (embedding de structs) e interfaces (contratos comportamentais).
Interfaces em Go são implícitas.
Um tipo satisfaz uma interface implementando seus métodos - sem palavra-chave implements.
Isso incentiva a definição de interfaces onde elas são consumidas, não onde os tipos são declarados.
A regra clássica é aceitar interfaces, retornar structs.
Chamadores dependem de comportamento restrito; implementações permanecem concretas e evoluíveis por trás de construtores como NewStore(cfg).
Dependências explícitas significam que uma função ou struct recebe o que precisa como parâmetros ou campos.
Globais, singletons em nível de pacote e cadeias pesadas de init() obscurecem o fluxo de dados e tornam os testes dependentes da ordem.
cmd/<binary>/main.go é a raiz de composição: o único lugar que deve conhecer bancos de dados, filas e roteadores HTTP concretos.
A direção da dependência deve apontar para dentro, em direção à lógica de domínio.
Handlers HTTP, servidores gRPC e comandos CLI ficam na borda.
Eles traduzem formatos de rede em tipos de domínio e chamam serviços que não sabem nada sobre tags JSON ou códigos de status.
Repositórios e clientes falam com o mundo exterior (SQL, S3, outras APIs HTTP).
Pacotes de domínio não devem importar net/http, drivers database/sql ou SDKs de fornecedores, a menos que o domínio realmente possua essa preocupação.
HTTP / gRPC / CLI (adaptadores)
|
v
serviços de aplicação (casos de uso)
|
v
tipos + regras de domínio
^
|
repositórios / gateways (portas implementadas por adaptadores)
Interfaces pequenas mantêm os mocks honestos.
Se Store tem doze métodos, cada "double" de teste implementa doze métodos - a maioria não utilizada.
Divida interfaces por necessidade do chamador: Reader, Writer, ou um único tipo de função PutObject.
O compilador ainda verifica a satisfação; testes trocam "fakes" sem codegen.
Limites de pacotes usam nomes de diretórios e internal/ para evitar "importações de amigos" entre equipes.
Um módulo de serviço pode expor example.com/billing publicamente, enquanto mantém example.com/billing/internal/postgres privado para o módulo.
Ciclos de importação são erros de compilação, não avisos - erros de arquitetura surgem cedo.
// Consumer define o contrato (geralmente 1-2 métodos).
type Ledger interface {
Post(ctx context.Context, entry Entry) error
}
// Service depende da interface, não de postgres ou mysql.
type Service struct {
ledger Ledger
}
func New(ledger Ledger) *Service {
return &Service{ledger: ledger}
}Microserviços escritos em Go ainda se beneficiam das mesmas regras in-process antes de dividir os binários.
Pacotes claros e interfaces se tornam as junções onde você posteriormente extrai um limite gRPC.
Extração prematura de serviço sem disciplina de pacotes geralmente copia importações emaranhadas para latência de rede.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Conexão manual de construtores | Grafo óbvio, zero mágica, compilações rápidas | main verboso, pontos de toque de refatoração | A maioria dos serviços e bibliotecas |
DI em tempo de compilação (wire) | Conexão gerada, ainda explícita | Etapa de codegen, curva de aprendizado | Grafos grandes com construtores estáveis |
Contêineres em tempo de execução (dig, fx) | Conveniente para aplicativos estilo plugin | Ordem de dependência oculta, análise estática mais difícil | Processos de longa duração com muitos componentes opcionais |
| Singletons globais | Primeiro commit rápido | Acoplamento oculto, testes frágeis | Apenas protótipos |
Frameworks (Gin, Echo, chi) pertencem à borda.
Registre rotas em main ou em um pacote internal/http fino; mantenha os pacotes de negócios agnósticos ao framework para que os testes chamem funções simples.
Observabilidade atravessa camadas através de middleware e contexto, não globais.
Passe context.Context para cancelamento; anexe loggers e IDs de rastreamento com slog ou APIs OpenTelemetry na borda.
Genéricos (Go 1.18+) reduzem a duplicação de helpers, mas não substituem os limites de interface para I/O e sistemas externos.
main simples ou wire; contêineres em tempo de execução são opcionais, não padrões idiomáticos.internal/ bloqueiam todo o uso externo." - Bloqueia importações fora da árvore pai; bibliotecas publicadas ainda precisam de uma API pública deliberada em pacotes não internos.Consumidores conhecem o comportamento mínimo de que precisam.
Provedores permanecem concretos e podem adicionar métodos sem quebrar chamadores que nunca importaram uma interface ampla.
Este padrão é Go idiomático, não um hack de teste.
Frequentemente um método para limites de I/O (Store, Publisher).
Dois ou três métodos quando um único chamador realmente precisa de um grupo coeso.
Se os testes stubam muitos métodos não utilizados, a interface é muito ampla.
Em cmd/<app>/main.go ou um pacote internal/wiring dedicado chamado apenas de main.
Bibliotecas devem expor construtores; aplicativos escolhem implementações concretas.
context.Context carrega valores e cancelamento em escopo de requisição.
Substitui globais thread-local para prazos e metadados de rastreamento.
Não oculte bancos de dados atrás do contexto; passe repositórios explicitamente.
Raramente: registros imutáveis carregados uma vez, padrões de biblioteca padrão ou métricas em todo o processo com ordem de inicialização clara.
Globais mutáveis para alças de DB ou configuração são um sinal de alerta.
Nenhum nome obrigatório.
Equipes usam domain, core ou pastas de recursos (billing, shipping).
Consistência dentro do seu módulo é mais importante do que o rótulo.
Uma porta Ledger restrita no monólito se torna um limite natural de serviço gRPC.
Structs amplas que misturam HTTP, SQL e métricas não se dividem de forma limpa.
A ordem de init() é implícita e difícil de testar.
Efeitos colaterais no momento da importação surpreendem os consumidores da biblioteca e lentificam binários que importam o pacote acidentalmente.
Prefira tipos de domínio dentro de serviços; mapeie linhas do driver na borda do repositório.
Vazar sql.NullString para handlers acopla HTTP ao esquema de armazenamento.
A aplicação de internal/ é por árvore de módulo.
Dividir módulos muda quais caminhos de importação estão "fora"; planeje os caminhos antes de publicar bibliotecas compartilhadas.
Sim - uma função com a assinatura correta satisfaz uma interface de tipo de função.
Útil para hooks (http.HandlerFunc, callbacks pequenos) sem declarar uma interface nomeada.
Mova contratos compartilhados para um pacote inferior que ambos importam, ou inverta a dependência para que apenas uma direção referencie a outra.
Ciclos significam que o desenho do limite está errado, não que você precisa de uma importação de contorno.
Versões de Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (mais recente - verifique na compilação), gin (mais recente - verifique na compilação), echo (mais recente - verifique na compilação), google.golang.org/grpc (mais recente - verifique na compilação), sigs.k8s.io/controller-runtime (mais recente - verifique na compilação), kubebuilder (mais recente - verifique na compilação), tinygo (mais recente - verifique os alvos de placa na compilação), wazero (mais recente - verifique na compilação) e golangci-lint (mais recente - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026