La simplicidad como restricción deliberada en el diseño de APIs
La restricción del lenguaje Go impulsa las APIs de bibliotecas y servicios hacia superficies pequeñas, dependencias explícitas y comportamiento predecible.
Busca en todas las páginas de la documentación
La restricción del lenguaje Go impulsa las APIs de bibliotecas y servicios hacia superficies pequeñas, dependencias explícitas y comportamiento predecible.
Eso no es una preferencia de estilo, es cómo los sistemas Go mantenibles sobreviven años de rotación de personal y actualizaciones de dependencias.
Go favorece las APIs aburridas: pocos constructores, interfaces estrechas, errores explícitos y paquetes que hacen una sola tarea.
Esa restricción acelera las revisiones, reduce las matrices de pruebas y mantiene las actualizaciones compatibles con la promesa de Go 1.
Esta página muestra cómo diseñar límites de paquetes y HTTP que coincidan con el lenguaje en lugar de luchar contra él.
Cuándo recurrir a esto: Estás publicando una biblioteca interna compartida, estabilizando un módulo público o refactorizando un "paquete dios" antes de un cambio de versión importante.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
// Interfaz del lado del consumidor - un método, definido donde se usa.
type ObjectStore interface {
Put(ctx context.Context, key string, body io.Reader) error
}
// El constructor devuelve el tipo concreto; mantén las opciones funcionales.
type S3Store struct { /* ... */ }
func NewS3Store(cfg Config) (*S3Store, error) { /* valida cfg */ return &S3Store{}, nil }
// La función acepta una interfaz, documenta los errores.
func UploadReport(ctx context.Context, store ObjectStore, r io.Reader) error {
if err := store.Put(ctx, "report.pdf", r); err != nil {
return fmt.Errorf("upload report: %w", err)
}
return nil
}Cuándo recurrir a esto:
Config con 20 campos.package checkout
import (
"context"
"errors"
"fmt"
"time"
)
// Los errores de dominio son centinelas a nivel de paquete - estables para errors.Is.
var ErrInsufficientFunds = errors.New("checkout: insufficient funds")
type PaymentGateway interface {
Charge(ctx context.Context, customerID string, cents int64) (chargeID string, err error)
}
type Service struct {
gw PaymentGateway
now func() time.Time
}
func New(gw PaymentGateway) *Service {
return &Service{gw: gw, now: time.Now}
}
type Request struct {
CustomerID string
Cents int64
}
func (s *Service) Pay(ctx context.Context, req Request) (string, error) {
if req.Cents <= 0 {
return "", fmt.Errorf("checkout: invalid amount %d", req.Cents)
}
id, err := s.gw.Charge(ctx, req.CustomerID, req.Cents)
if err != nil {
return "", fmt.Errorf("checkout: charge: %w", err)
}
return id, nil
}Lo que esto demuestra:
PaymentGateway) mantiene los fakes de un método de ancho.New documenta las dependencias requeridas sin un framework de DI.Request agrupa las entradas en lugar de aumentar la aridad de la función.now muestra cómo probar el tiempo sin exportar globales.implements de boilerplate.%w vincula las causas mientras se preservan las comprobaciones de centinelas a través de errors.Is y las comprobaciones de tipo a través de errors.As.context.Context es el primer parámetro para la cancelación y los valores con ámbito de solicitud en los límites de E/S.| Regla | Razón | Olor de violación |
|---|---|---|
| Aceptar interfaces, devolver structs | Facilidad de prueba sin mocks amplios | Interfaz exportada con 10 métodos |
| Una tarea por paquete | Grafos de importación claros | Paquete util con ayudantes no relacionados |
| Opciones funcionales para perillas raras | Evitar Config de 12 campos | New(a,b,c,d,e,f...) |
| Errores estables como centinelas | Enrutamiento de alertas SRE | Coincidencia de cadenas en logs |
Pasar context en E/S | Cancelación uniforme | context.Background() oculto dentro de la biblioteca |
| Capa | Mantener simple | Posponer complejidad |
|---|---|---|
| Handler | Decodificar, autorizar, llamar al servicio, mapear errores a estados | Reglas de negocio |
| Servicio | Orquestar pasos de dominio, transacciones | Detalles de SQL |
| Repositorio | CRUD con contexto | Política |
Para gRPC, prefiere mensajes protobuf que reflejen los sustantivos del dominio, no todos los DTO internos.
Mapea los errores de dominio a codes.InvalidArgument, codes.NotFound y codes.Internal en un solo lugar.
// Opción funcional - escala sin explosión de Config.
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(opts ...Option) *Server {
s := &Server{timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}Start - mutar servidores en ejecución es un pie de foso común.Config gigantes - los llamadores no conocen los campos requeridos; los valores cero los configuran silenciosamente de forma incorrecta. Solución: validar en New, devolver error, proporcionar valores predeterminados solo para perillas seguras.*sql.DB a través de cada capa - acopla el dominio al almacenamiento. Solución: interfaz de repositorio con tipos de dominio en el borde del servicio.internal/platform/core sin llamadores. Solución: esperar al segundo consumidor antes de extraer.context.Context por método, nunca guardarlo en campos./v2) para cambios incompatibles en la API. Solución: planificar v2 al eliminar o renombrar exportaciones.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Opciones funcionales | Varios parámetros de ajuste opcionales | Solo una o dos dependencias requeridas - usar New(dep) |
| Patrón Builder (encadenado) | DSL fluido para fixtures de prueba complejas | Constructores de producción - las opciones son más idiomáticas |
| Frameworks pesados de DI (wire, dig) | Grafos muy grandes con generación de código | Servicios pequeños - el cableado manual en main es más claro |
APIs genéricas (func Do[T any]) | Duplicación real entre tipos | Funciones de un solo tipo - los genéricos añaden ruido |
Singletons globales (var DB) | Prototipos rápidos | Bibliotecas y servicios revisados para producción |
Los productores no deben depender de la abstracción de cada consumidor.
Las interfaces pequeñas del consumidor documentan exactamente lo que el llamador necesita y mantienen los mocks mínimos.
No hay un límite fijo - apunta a una responsabilidad coherente.
Si godoc se lee como un catálogo de utilidades no relacionadas, divide los paquetes.
Cuando la mayoría de los llamadores aceptan los valores predeterminados y una minoría ajusta tiempos de espera, URLs o credenciales.
Las dependencias requeridas pertenecen a los parámetros de New, no a las opciones.
Usa sufijos de versión principal de módulo (/v2) y mantén las rutas de importación de v1 estables según la promesa de compatibilidad de Go 1 para tu propia línea v1.
Devuelve errores envueltos con centinelas documentados para su clasificación.
Exporta tipos de error solo cuando los llamadores necesiten campos estructurados más allá de errors.As.
Delgados: analiza la entrada, llama a un método de servicio, traduce errores a códigos de estado.
Si los manejadores superan las ~40 líneas, mueve la ramificación a servicios probables.
No.
Usa genéricos cuando eliminen duplicación real; evítalos cuando una sola función se lea más claramente.
Comparte contratos protobuf/OpenAPI, no gigantescas structs de configuración compartidas de Go.
Mantén el ajuste específico del servicio local; comparte solo sustantivos estables entre servicios.
Los paquetes expresan límites; los archivos organizan la legibilidad dentro de un límite.
Divide los paquetes cuando aparezcan ciclos de importación o conceptos no relacionados, no cuando los archivos se sientan largos por sí solos.
Usa comentarios de godoc en símbolos exportados, entradas de CHANGELOG y etiquetas de módulo.
Llama a las APIs experimentales Experimental en el nombre o documenta la inestabilidad en el comentario del paquete.
Exporta campos cuando los invariantes sean simples.
Usa métodos cuando se requiera validación, bloqueo o valores derivados - no simetría de JavaBean.
Las APIs simples pueden ocultar pooling o batching dentro del paquete.
Perfila antes de agregar complejidad a las superficies exportadas - mantén las optimizaciones sin exportar.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado de GC de Green Tea, go fix modernizers - verifica el parche en la compilación), chi (última - verifica en la compilación), gin (última - verifica en la compilación), echo (última - verifica en la compilación), google.golang.org/grpc (última - verifica en la compilación), sigs.k8s.io/controller-runtime (última - verifica en la compilación), kubebuilder (última - verifica en la compilación), tinygo (última - verifica los objetivos de la placa en la compilación), wazero (última - verifica en la compilación) y golangci-lint (última - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026