Shared Kernel
Cómo compartir un subconjunto controlado de modelo de dominio entre bounded contexts para reducir duplicación sin sacrificar la autonomía de los equipos.
Qué problema resuelve
En una arquitectura de microservicios con múltiples bounded contexts, cada contexto tiene su propio modelo de dominio. Pero a veces, dos o más contextos necesitan compartir ciertos conceptos que son idénticos en ambos lados:
Sin Shared Kernel:
Order Service Shipping Service
┌────────────────┐ ┌────────────────┐
│ Address { │ │ Address { │
│ street │ │ street │
│ city │ │ city │
│ state │ │ state │
│ zipCode │ │ zipCode │
│ country │ │ country │
│ validate() │ │ validate() │
│ } │ │ } │
└────────────────┘ └────────────────┘
Misma clase duplicada en ambos servicios
Si cambia la validación, hay que cambiar en ambos
Riesgo de divergencia silenciosa
Los problemas son:
- Duplicación: El mismo código se mantiene en múltiples lugares
- Divergencia: Con el tiempo, las copias evolucionan de forma diferente y se vuelven inconsistentes
- Esfuerzo de sincronización: Cada cambio debe replicarse manualmente en todos los contextos
- Bugs sutiles: Una validación actualizada en un servicio pero no en otro causa inconsistencias
Cómo funciona
El Shared Kernel define un subconjunto pequeño y explícito del modelo de dominio que se comparte entre dos o más bounded contexts. Este kernel compartido se gestiona como una librería o paquete independiente con versionado propio.
Con Shared Kernel:
┌─────────────────────────────────┐
│ Shared Kernel │
│ (librería versionada) │
│ │
│ Address { street, city, ... } │
│ Money { amount, currency } │
│ DateRange { from, to } │
│ OrderStatus enum │
└─────────┬───────────┬───────────┘
│ │
▼ ▼
Order Service Shipping Service
(usa v2.1.0) (usa v2.1.0)
Qué incluir en el Shared Kernel
| Incluir | No incluir |
|---|---|
| Value Objects compartidos (Address, Money, DateRange) | Entidades con lógica de negocio específica |
| Enums de estado compartidos (OrderStatus) | Repositorios o servicios de aplicación |
| DTOs de eventos de integración | Lógica de negocio de un solo contexto |
| Interfaces de contratos (API contracts) | Modelos de persistencia (entidades de BD) |
| Validaciones comunes | Configuración específica de infraestructura |
Reglas del Shared Kernel
El Shared Kernel funciona bajo reglas estrictas para evitar que se convierta en un problema:
- Mínimo necesario: Solo incluir lo que realmente se comparte entre contextos
- Propiedad compartida: Ambos equipos deben acordar cualquier cambio
- Versionado semántico: Cambios breaking requieren nueva versión major
- Tests compartidos: El kernel tiene sus propios tests que ambos equipos ejecutan
- Revisión conjunta: Los cambios al kernel requieren aprobación de todos los equipos consumidores
Ejemplo: Shared Kernel como paquete
shared-kernel/
├── src/
│ ├── value-objects/
│ │ ├── Address.ts
│ │ ├── Money.ts
│ │ └── DateRange.ts
│ ├── events/
│ │ ├── OrderCreatedEvent.ts
│ │ └── PaymentProcessedEvent.ts
│ ├── enums/
│ │ ├── OrderStatus.ts
│ │ └── PaymentMethod.ts
│ └── index.ts
├── tests/
│ ├── Address.test.ts
│ ├── Money.test.ts
│ └── DateRange.test.ts
├── package.json (version: "2.1.0")
└── CHANGELOG.md
Cada servicio lo consume como dependencia:
// Order Service - package.json
{
"dependencies": {
"@company/shared-kernel": "^2.1.0"
}
}
// Shipping Service - package.json
{
"dependencies": {
"@company/shared-kernel": "^2.1.0"
}
}
Evolución del Shared Kernel
Versión 1.0.0: Address, Money
Versión 1.1.0: + DateRange (backward compatible)
Versión 2.0.0: Address cambia validación de zipCode (breaking change)
→ Ambos equipos deben actualizar y testear
→ Se coordina el despliegue
Shared Kernel vs otras estrategias
| Estrategia | Acoplamiento | Autonomía | Cuándo usar |
|---|---|---|---|
| Shared Kernel | Medio (controlado) | Media | Conceptos idénticos en 2-3 contextos |
| ACL (Anti-Corruption Layer) | Bajo | Alta | Modelos diferentes que necesitan traducción |
| Copiar y divergir | Ninguno | Total | Conceptos similares pero que evolucionan diferente |
| Servicio compartido | Alto | Baja | Funcionalidad completa compartida |
Ventajas
- Elimina duplicación: Un solo lugar para código compartido
- Consistencia garantizada: Todos los contextos usan la misma versión del modelo
- Evolución coordinada: Los cambios se propagan de forma controlada
- Testing centralizado: Los tests del kernel validan el comportamiento compartido
- Contratos explícitos: El kernel define formalmente qué se comparte
Trade-offs / Desventajas
- Acoplamiento entre equipos: Los cambios al kernel requieren coordinación entre equipos
- Velocidad de desarrollo: Un equipo puede bloquearse esperando un cambio en el kernel
- Riesgo de crecimiento: Si el kernel crece demasiado, se convierte en un monolito compartido
- Versionado complejo: Gestionar versiones y compatibilidad entre consumidores requiere disciplina
- Despliegue coordinado: Cambios breaking requieren que todos los consumidores actualicen
- Governance: Necesita reglas claras sobre quién puede modificar el kernel y cómo
Cuándo usar
- Dos o más bounded contexts comparten value objects idénticos (Address, Money, DateRange)
- Eventos de integración que deben tener el mismo schema en productor y consumidor
- Enums de estado que deben ser consistentes entre servicios
- Cuando la duplicación causa bugs frecuentes por divergencia
- Equipos que trabajan en dominios cercanos y pueden coordinarse fácilmente
Cuándo evitar
- Más de 3-4 contextos consumidores (la coordinación se vuelve inmanejable)
- Cuando los conceptos “compartidos” en realidad tienen semánticas diferentes en cada contexto
- Equipos distribuidos geográficamente con poca comunicación
- Cuando la autonomía de los equipos es más importante que evitar duplicación
- Si el kernel tiende a crecer sin control (señal de que los bounded contexts están mal definidos)
Tecnologías e implementaciones comunes
| Categoría | Opciones |
|---|---|
| Distribución | npm packages, Maven artifacts, NuGet packages, Git submodules |
| Versionado | Semantic Versioning (SemVer), Conventional Commits |
| Monorepo | Nx, Turborepo, Lerna (kernel como paquete interno) |
| Registro | npm registry privado, Artifactory, GitHub Packages |
| CI/CD | Pipeline dedicado para el kernel con tests de compatibilidad |
Relación con otros patrones
- DDD (Bounded Context): El Shared Kernel es una relación entre bounded contexts definida por Eric Evans
- Anti-Corruption Layer: Alternativa al Shared Kernel cuando los modelos son diferentes
- Event-Driven Architecture: Los schemas de eventos de integración son candidatos naturales para el kernel
- Microservicios: El Shared Kernel debe ser pequeño para no comprometer la independencia de los servicios
- Contratos (API Contracts): Los contratos de API pueden vivir en el Shared Kernel
Próximos pasos
El Shared Kernel es una herramienta poderosa pero que requiere disciplina. Para entender la alternativa cuando los modelos son incompatibles, revisa el Anti-Corruption Layer. Para ver cómo los eventos de integración se benefician del kernel compartido, explora Event-Driven Architecture.