Padrões de Camada de Repositório e Serviço
As camadas de repositório e serviço separam o acesso a dados da lógica de negócios para que os serviços Go permaneçam testáveis sem a necessidade de levantar o Postgres em todos os testes unitários.
Busque em todas as páginas da documentação
As camadas de repositório e serviço separam o acesso a dados da lógica de negócios para que os serviços Go permaneçam testáveis sem a necessidade de levantar o Postgres em todos os testes unitários.
Handlers traduzem HTTP ou gRPC em chamadas de serviço; repositórios traduzem as necessidades do serviço em operações SQL, RPC ou de cache.
Um repositório oculta como as linhas são carregadas e salvas.
Um serviço orquestra regras: validação, verificações de autorização, idempotência e fluxos de trabalho de várias etapas.
A camada de handler lida com preocupações de transporte: códigos de status, cabeçalhos e análise de requisição.
Interfaces são definidas onde são consumidas (tipicamente o pacote de serviço) e satisfeitas por implementações concretas de repositório em internal/storage ou similar.
Cartão de referência rápida - pronto para copiar e colar.
type UserRepo interface {
Get(ctx context.Context, id string) (User, error)
}
type UserService struct {
repo UserRepo
}
func (s *UserService) DisplayName(ctx context.Context, id string) (string, error) {
u, err := s.repo.Get(ctx, id)
if err != nil { return "", err }
return strings.TrimSpace(u.First + " " + u.Last), nil
}Quando usar isso:
package app
import (
"context"
"errors"
"fmt"
)
type Order struct {
ID string
UserID string
Total int64
Status string
}
var ErrNotFound = errors.New("não encontrado")
type OrderRepo interface {
Get(ctx context.Context, id string) (Order, error)
Save(ctx context.Context, o Order) error
}
type OrderService struct {
repo OrderRepo
}
func NewOrderService(repo OrderRepo) *OrderService {
return &OrderService{repo: repo}
}
func (s *OrderService) Cancel(ctx context.Context, id string) error {
o, err := s.repo.Get(ctx, id)
if err != nil {
return err
}
if o.Status == "shipped" {
return fmt.Errorf("não é possível cancelar pedido enviado")
}
o.Status = "cancelled"
return s.repo.Save(ctx, o)
}
// PostgresRepo vive em internal/storage; testes usam memRepo.
type memRepo struct {
data map[string]Order
}
func (m *memRepo) Get(_ context.Context, id string) (Order, error) {
o, ok := m.data[id]
if !ok { return Order{}, ErrNotFound }
return o, nil
}
func (m *memRepo) Save(_ context.Context, o Order) error {
m.data[o.ID] = o
return nil
}
func Example() {
svc := NewOrderService(&memRepo{data: map[string]Order{
"1": {ID: "1", Status: "pending"},
}})
_ = svc.Cancel(context.Background(), "1")
}O que isso demonstra:
OrderService codifica a regra de cancelamentoOrderRepo é uma interface estreita com dois métodosHTTP handler --> Serviço --> Repositório --> database/driver
| | |
transporte negócios persistência
main conecta repositórios concretos em serviços e serviços em handlers| Estilo | Prós | Contras |
|---|---|---|
Repositório de entidade (UserRepo) | Propriedade clara por agregado | Muitas interfaces em domínios grandes |
| Interface de armazenamento por contexto delimitado | Menos tipos | Pode crescer amplamente sem cuidado |
| Parâmetros de função para operações únicas | Mínimo | Não escala além de uma função |
ErrNotFound ou erros de driver encapsuladoscannot cancel shipped order)404, 409, 500 usando errors.Is / errors.As// Conectado em cmd/api/main.go
repo := postgres.NewOrderRepo(pool)
svc := app.NewOrderService(repo)
handler := httpapi.NewOrdersHandler(svc)context.Context por todas as camadas para cancelamento e rastreamento*sql.Rows de repositórios - retorne structs de domínioService com 40 métodos espelha um pacote "Deus". Correção: divida por contexto delimitado (OrderService, BillingService).ctx como primeiro parâmetro onde I/O pode bloquear.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Active Record (métodos na struct carregam a si mesmos) | Pequenos aplicativos CRUD, protótipos | Regras complexas ou múltiplos backends de armazenamento |
| Divisão CQRS de leitura/escrita | Modelos de leitura diferem muito das escritas | CRUD simples com um banco de dados |
| Repositório + serviço (este padrão) | Serviços testáveis, armazenamento em evolução | Scripts com menos de 200 linhas |
| Handler chama SQL diretamente | Ferramentas de administração únicas | APIs de produção com regras de negócios |
Não - programas pequenos podem chamar SQL de handlers.
Extraia camadas quando os testes ou o tamanho da equipe justificarem a fronteira.
No pacote consumidor (serviço) que chama o comportamento.
A implementação Postgres vive no armazenamento sem importar o serviço.
Prefira por raiz agregada (OrderRepo carrega pedidos e linhas juntos) para evitar cargas parciais prolixas.
Adicione WithTx(ctx, fn func(ctx.Context) error) no repositório ou passe Tx através do contexto em pacotes internos - mantenha a API do serviço estável.
Use erros sentinela ou tipados em pacotes de domínio; handlers traduzem para códigos de transporte.
Se um método orquestra mais de uma chamada de repositório mais regras, ele pertence a um serviço.
Formatação pura permanece nos tipos de domínio.
Mesma camada de serviço; apenas o adaptador externo muda (servidor grpc vs handler chi).
Passe repositórios falsos que registram chamadas; teste em tabela as regras de negócios sem contêineres de banco de dados.
O cache é uma preocupação de decoração - envolva o repositório com uma camada de cache que implemente a mesma interface.
Adicione métodos de consulta ao repositório ou a uma interface OrderQuery separada se os caminhos de leitura dominarem e diferirem das escritas.
Versões da 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 (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026