Patrones de Capa de Repositorio y Servicio
Las capas de repositorio y servicio separan el acceso a datos de la lógica de negocio para que los servicios Go permanezcan testeables sin necesidad de levantar Postgres en cada prueba unitaria.
Busca en todas las páginas de la documentación
Las capas de repositorio y servicio separan el acceso a datos de la lógica de negocio para que los servicios Go permanezcan testeables sin necesidad de levantar Postgres en cada prueba unitaria.
Los manejadores traducen HTTP o gRPC a llamadas de servicio; los repositorios traducen las necesidades del servicio a operaciones SQL, RPC o de caché.
Un repositorio oculta cómo se cargan y guardan las filas.
Un servicio orquesta reglas: validación, comprobaciones de autorización, idempotencia y flujos de trabajo de varios pasos.
La capa de manejador se ocupa de las preocupaciones de transporte: códigos de estado, encabezados y análisis de solicitudes.
Las interfaces se definen donde se consumen (típicamente el paquete de servicio) y son satisfechas por implementaciones concretas de repositorio en internal/storage o similar.
Tarjeta de referencia rápida - lista para copiar y pegar.
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
}Cuándo usar esto:
package app
import (
"context"
"errors"
"fmt"
)
type Order struct {
ID string
UserID string
Total int64
Status string
}
var ErrNotFound = errors.New("not found")
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("cannot cancel shipped order")
}
o.Status = "cancelled"
return s.repo.Save(ctx, o)
}
// PostgresRepo vive en internal/storage; las pruebas usan 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")
}Lo que esto demuestra:
OrderService codifica la regla de cancelaciónOrderRepo es una interfaz estrecha con dos métodosManejador HTTP --> Servicio --> Repositorio --> base de datos/driver
| | |
transporte negocio persistencia
main une repositorios concretos en servicios y servicios en manejadores| Estilo | Pros | Contras |
|---|---|---|
Repositorio de entidad (UserRepo) | Propiedad clara por agregado | Muchas interfaces en dominios grandes |
| Interfaz de almacenamiento por contexto delimitado | Menos tipos | Puede crecer ampliamente sin cuidado |
| Parámetros de función para operaciones únicas | Mínimo | No escala más allá de una función |
ErrNotFound o errores de driver envueltoscannot cancel shipped order)404, 409, 500 usando errors.Is / errors.As// Se integra en cmd/api/main.go
repo := postgres.NewOrderRepo(pool)
svc := app.NewOrderService(repo)
handler := httpapi.NewOrdersHandler(svc)context.Context a través de cada capa para cancelación y rastreo*sql.Rows de los repositorios - devuelva structs de dominiodatabase/sql y se vuelve in-testable. Solución: interfaz de repositorio en el paquete de servicio; SQL en internal/storage.Service con 40 métodos refleja un paquete "dios". Solución: dividir por contexto delimitado (OrderService, BillingService).ctx como primer parámetro en cualquier lugar donde la E/S pueda bloquearse.| Alternativa | Úselo Cuando | No lo Use Cuando |
|---|---|---|
| Active Record (métodos en el struct se cargan a sí mismos) | Aplicaciones CRUD diminutas, prototipos | Reglas complejas o múltiples backends de almacenamiento |
| División CQRS de lectura/escritura | Los modelos de lectura difieren mucho de las escrituras | CRUD simple con una base de datos |
| Repositorio + servicio (este patrón) | Servicios testeables, almacenamiento en evolución | Scripts de menos de 200 líneas |
| El manejador llama directamente a SQL | Herramientas de administración únicas | APIs de producción con reglas de negocio |
No, los programas pequeños pueden llamar a SQL desde los manejadores.
Extraiga capas cuando las pruebas o el tamaño del equipo justifiquen el límite.
En el paquete consumidor (servicio) que llama al comportamiento.
La implementación de Postgres vive en storage sin importar service.
Prefiera por raíz de agregado (OrderRepo carga pedidos y líneas juntos) para evitar cargas parciales y ruidosas.
Agregue WithTx(ctx, fn func(ctx.Context) error) en el repositorio o pase Tx a través del contexto en paquetes internos; mantenga la API del servicio estable.
Use errores centinela o tipados en los paquetes de dominio; los manejadores traducen a códigos de transporte.
Si un método orquesta más de una llamada de repositorio además de reglas, pertenece a un servicio.
El formato puro permanece en los tipos de dominio.
La misma capa de servicio; solo cambia el adaptador exterior (servidor grpc vs manejador chi).
Pase repositorios falsos que registren llamadas; pruebe las reglas de negocio con tablas sin contenedores de bases de datos.
El almacenamiento en caché es una preocupación de decoración; envuelva el repositorio con una capa de caché que implemente la misma interfaz.
Agregue métodos de consulta al repositorio o a una interfaz OrderQuery separada si las rutas de lectura dominan y difieren de las escrituras.
mainVersiones de Stack: Esta página fue escrita para Go 1.26.x (GC por defecto Green Tea, go fix modernizers - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026