Arquitectura en Go: Interfaces Pequeñas, Dependencias Explícitas
Go no incluye herencia, anotaciones ni un contenedor de inyección de dependencias en tiempo de ejecución.
Busca en todas las páginas de la documentación
Go no incluye herencia, anotaciones ni un contenedor de inyección de dependencias en tiempo de ejecución.
Esas ausencias son características: impulsan a los equipos hacia interfaces pequeñas, cableado de constructores y límites de paquetes que compilan limpiamente y se mantienen testeables a medida que los servicios crecen.
internal/, composición, funciones constructoras, superficie de API de paquetes.init().main; sin cableado mágico transversal; la disciplina de arquitectura se aplica por convención y revisión, no solo por el compilador.Los paquetes de Go son la unidad principal de reutilización.
Un paquete exporta un conjunto de identificadores; todo lo demás permanece privado.
No hay subclases: el comportamiento se comparte a través de la composición (incrustación de structs) y las interfaces (contratos de comportamiento).
Las interfaces en Go son implícitas.
Un tipo satisface una interfaz implementando sus métodos; no hay palabra clave implements.
Eso fomenta definir interfaces donde se consumen, no donde se declaran los tipos.
La regla clásica es aceptar interfaces, devolver structs.
Los llamadores dependen de un comportamiento reducido; las implementaciones permanecen concretas y evolutivas detrás de constructores como NewStore(cfg).
Las dependencias explícitas significan que una función o struct recibe lo que necesita como parámetros o campos.
Los globales, los singletons a nivel de paquete y las cadenas pesadas de init() oscurecen el flujo de datos y hacen que las pruebas dependan del orden.
cmd/<binary>/main.go es la raíz de composición: el único lugar que debería conocer bases de datos, colas y enrutadores HTTP concretos.
La dirección de las dependencias debe apuntar hacia adentro, hacia la lógica de dominio.
Los manejadores HTTP, servidores gRPC y comandos CLI se sientan en el borde.
Traducen formatos de red a tipos de dominio y llaman a servicios que no saben nada sobre etiquetas JSON o códigos de estado.
Los repositorios y clientes hablan con el mundo exterior (SQL, S3, otras APIs HTTP).
Los paquetes de dominio no deben importar net/http, controladores database/sql o SDKs de terceros a menos que el dominio realmente posea esa preocupación.
HTTP / gRPC / CLI (adaptadores)
|
v
servicios de aplicación (casos de uso)
|
v
tipos + reglas de dominio
^
|
repositorios / gateways (puertos implementados por adaptadores)
Las interfaces pequeñas mantienen los mocks honestos.
Si Store tiene doce métodos, cada doble de prueba implementa doce métodos, la mayoría sin usar.
Divide las interfaces según las necesidades del llamador: Reader, Writer o un tipo de función PutObject único.
El compilador todavía verifica la satisfacción; las pruebas intercambian fakes sin generación de código.
Los límites de los paquetes utilizan nombres de directorios y internal/ para evitar "importaciones de amigos" entre equipos.
Un módulo de servicio podría exponer públicamente example.com/billing mientras mantiene example.com/billing/internal/postgres privado para el módulo.
Los ciclos de importación son errores de compilación, no advertencias; los errores de arquitectura se manifiestan temprano.
// Consumer define el contrato (a menudo 1-2 métodos).
type Ledger interface {
Post(ctx context.Context, entry Entry) error
}
// Service depende de la interfaz, no de postgres o mysql.
type Service struct {
ledger Ledger
}
func New(ledger Ledger) *Service {
return &Service{ledger: ledger}
}Los microservicios escritos en Go aún se benefician de las mismas reglas dentro del proceso antes de dividir los binarios.
Los paquetes y interfaces claros se convierten en las uniones donde luego se extrae un límite gRPC.
La extracción prematura de servicios sin disciplina de paquetes generalmente copia importaciones enredadas en latencia de red.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Cableado manual de constructores | Grafo obvio, cero magia, compilaciones rápidas | main verboso, puntos de refactorización | La mayoría de los servicios y bibliotecas |
DI en tiempo de compilación (wire) | Cableado generado, aún explícito | Paso de generación de código, curva de aprendizaje | Grafos grandes con constructores estables |
Contenedores en tiempo de ejecución (dig, fx) | Conveniente para aplicaciones estilo plugin | Orden de dependencias oculto, análisis estático más difícil | Procesos de larga duración con muchos componentes opcionales |
| Singletons globales | Primer commit rápido | Acoplamiento oculto, pruebas frágiles | Solo prototipos |
Los frameworks (Gin, Echo, chi) pertenecen al borde.
Registra rutas en main o en un paquete internal/http delgado; mantén los paquetes de negocio independientes del framework para que las pruebas llamen a funciones simples.
La observabilidad cruza capas a través de middleware y contexto, no globales.
Pasa context.Context para cancelación; adjunta loggers e IDs de traza con slog o APIs de OpenTelemetry en el borde.
Los genéricos (Go 1.18+) reducen la duplicación de ayudantes pero no reemplazan los límites de interfaz para I/O y sistemas externos.
main o wire; los contenedores en tiempo de ejecución son opcionales, no valores predeterminados idiomáticos.internal/ bloquean todo uso externo." - Bloquean importaciones fuera del árbol padre; las bibliotecas publicadas aún necesitan una API pública deliberada en paquetes no internos.Los consumidores conocen el comportamiento mínimo que necesitan.
Los proveedores permanecen concretos y pueden agregar métodos sin romper a los llamadores que nunca importaron una interfaz amplia.
Este patrón es idiomático en Go, no un truco de pruebas.
A menudo un método para límites de I/O (Store, Publisher).
Dos o tres métodos cuando un solo llamador realmente necesita un grupo cohesivo.
Si las pruebas simulan muchos métodos no utilizados, la interfaz es demasiado amplia.
En cmd/<app>/main.go o en un paquete dedicado internal/wiring llamado solo desde main.
Las bibliotecas deben exponer constructores; las aplicaciones eligen implementaciones concretas.
context.Context transporta valores y cancelación específicos de la solicitud.
Reemplaza los globales de hilo local para plazos y metadatos de traza.
No ocultes bases de datos detrás del contexto; pasa los repositorios explícitamente.
Raramente: registros inmutables cargados una vez, patrones de biblioteca estándar o métricas a nivel de proceso con un orden de inicialización claro.
Los globales mutables para manejadores de DB o configuración son una señal de alerta.
No hay un nombre obligatorio.
Los equipos usan domain, core o carpetas de características (billing, shipping).
La consistencia dentro de tu módulo es más importante que la etiqueta.
Un puerto Ledger estrecho en el monolito se convierte en un límite de servicio gRPC natural.
Los structs amplios que mezclan HTTP, SQL y métricas no se dividen limpiamente.
El orden de init() es implícito y difícil de probar.
Los efectos secundarios en el momento de la importación sorprenden a los consumidores de la biblioteca y ralentizan los binarios que importan el paquete accidentalmente.
Prefiere tipos de dominio dentro de los servicios; mapea las filas del controlador en el borde del repositorio.
Filtrar sql.NullString en los manejadores acopla HTTP al esquema de almacenamiento.
La aplicación de internal/ es por árbol de módulos.
Dividir módulos cambia qué rutas de importación están "fuera"; planifica las rutas antes de publicar bibliotecas compartidas.
Sí, una función con la firma correcta satisface una interfaz de tipo función.
Útil para hooks (http.HandlerFunc, callbacks pequeños) sin declarar una interfaz nombrada.
Mueve los contratos compartidos a un paquete inferior que ambos importan, o invierte la dependencia para que solo una dirección haga referencia a la otra.
Los ciclos significan que el dibujo del límite es incorrecto, no que necesitas una importación de solución alternativa.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea, modernizadores go fix - 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 conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026