DDD y Clean Architecture en Go: Un enfoque pragmático sin sobreingeniería
La deuda técnica derivada de problemas arquitectónicos consume hasta el 40% de los presupuestos de TI de las empresas. En proyectos Go, esto a menudo se manifiesta como estructuras excesivamente complejas: 200 archivos llenos de interfaces vacías y capas donde la lógica de negocio queda enterrada. La causa raíz es el mal uso de patrones DDD y Clean Architecture tomados prestados de Java o C#. Pero cuando se aplican de manera pragmática, estos patrones resuelven problemas de complejidad. Aquí te explico cómo evitar la sobreingeniería y construir un sistema mantenible.
¿Por qué los proyectos Go se convierten en un laberinto de archivos?
Los desarrolladores que llegan a Go desde otros lenguajes suelen arrastrar hábitos arquitectónicos que no encajan con el Go idiomático. Según la Go Developer Survey 2024, el principal problema es mantener estándares de código consistentes debido a niveles de experiencia variados y patrones no idiomáticos. Por ejemplo, crear estructuras de carpetas como domain, application e infrastructure imitando Java, sin captar los principios fundamentales.
La investigación de Carnegie Mellon lo confirma: los problemas arquitectónicos son la principal fuente de deuda técnica. En Go, se ve así:
- Una carpeta
dtocon structs duplicados. mappersentre capas para conversiones triviales.- Interfaces en
domainque dependen de infrastructure (p. ej.,database/sql). - Casos de uso que solo llaman a repositorios sin lógica de negocio.
El resultado: 200 archivos, pero ninguno que describa reglas de negocio reales. Clean Architecture y DDD se convierten no en la solución, sino en el problema mismo.
DDD: No se trata de carpetas, sino de lenguaje y límites
Domain-Driven Design no se trata de un conjunto de carpetas, sino de un enfoque para modelar el dominio del problema. Conceptos clave:
Lenguaje Ubicuo
Los desarrolladores y las partes interesadas del negocio deben usar los mismos términos en todas partes: en el código, la documentación y las discusiones. Si un gerente dice "confirmar pedido", el código debería tener un método Confirm(), no SetStatus(). Esto elimina confusiones y acelera la incorporación.
Contexto Acotado
Un sistema grande consta de múltiples contextos. Por ejemplo, "cliente" en ventas (un prospecto con embudo de ventas) y en soporte (tickets) son modelos diferentes. El Contexto Acotado define límites claros donde el modelo se mantiene consistente. En microservicios, un servicio típicamente se alinea con un contexto.
Entidad y Objeto de Valor
- Entidad tiene un ID único y mantiene identidad (p. ej.,
Order). Contiene reglas de negocio. - Objeto de Valor es inmutable y definido por sus atributos (p. ej.,
Money). Dos objetos con campos idénticos son equivalentes.
Ejemplo de Objeto de Valor en Go:
type Money struct {
Amount int64
Currency string
}
func (m Money) Add(other Money) (Money, error) {
if m.Currency != other.Currency {
return Money{}, errors.New("currency mismatch")
}
return Money{Amount: m.Amount + other.Amount, Currency: m.Currency}, nil
}
Agregado
Un grupo de objetos tratados como una unidad única. Tiene una raíz (Raíz de Agregado) a través de la cual ocurre todo acceso. Por ejemplo, Order (raíz) incluye OrderItem. Regla: los cambios se hacen solo a través de la raíz, asegurando consistencia.
Clean Architecture: La regla de dependencia como fundamento
La esencia de Clean Architecture es que la lógica de negocio no debería depender de detalles de implementación (bases de datos, frameworks). La única regla: las dependencias apuntan hacia adentro (Regla de Dependencia).
Cómo funciona en Go
- domain: Contiene el modelo de negocio (Entidad, Objeto de Valor, interfaces de repositorio). Sin importaciones externas excepto stdlib.
- application: Casos de uso que orquestan el dominio. Depende solo de domain.
- infrastructure: Implementaciones de adaptadores (bases de datos, clientes HTTP). Depende de domain vía interfaces.
- delivery: Puntos de entrada (HTTP, gRPC). Depende de application.
Si domain importa database/sql, la arquitectura está rota. Ejemplo correcto: interfaz de repositorio declarada en domain, implementación en infrastructure/postgres.
Beneficios de combinar DDD y Clean Architecture
| Aspecto | Efecto | Métrica de mejora |
|----------------------|--------------------------------------|-------------------|
| Testabilidad | Aislamiento del dominio de infrastructure | +40% cobertura |
| Flexibilidad | Cambiar adaptadores en horas | -90% tiempo |
| Comprensibilidad | Límites claros de componentes | -70% incorporación|
Estructura real de proyecto: Evitando 200 archivos
Estructura óptima para un servicio Go:
internal/
domain/
order.go # Agregado Order
order_repo.go # Interfaz de repositorio
application/
create_order.go # Caso de uso
infrastructure/
postgres/
order_repo.go # Implementación
delivery/
http/
order_handler.go
Principios clave:
- Interfaces de repositorio en
domain, no eninfrastructure. - Sin carpetas
dtoomappersa menos que sean realmente necesarias: reutiliza las mismas structs a través de capas si no viola límites. - Lógica de negocio solo en
domain. Por ejemplo, métodoCancel()paraOrderverifica el estado:
func (o *Order) Cancel() error {
if o.status == StatusShipped {
return errors.New("cannot cancel shipped order")
}
o.status = StatusCancelled
return nil
}
Puntos clave
- Lenguaje Ubicuo es el fundamento de DDD. Si los términos del código no coinciden con el lenguaje del negocio, el modelo estará mal.
- Regla de Dependencia es el núcleo de Clean Architecture. Las violaciones llevan a código espagueti.
- Pragmatismo — no crees capas sin necesidad. En Go, a menudo puedes omitir
dtosi las structs se alinean. - Agregados definen límites de transacción. Cambia solo a través de la raíz para consistencia.
- Pruebas — las pruebas de dominio se ejecutan sin infrastructure, acelerando el desarrollo.
— Editorial Team
Aún no hay comentarios.