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

IncluirNo 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ónLógica de negocio de un solo contexto
Interfaces de contratos (API contracts)Modelos de persistencia (entidades de BD)
Validaciones comunesConfiguración específica de infraestructura

Reglas del Shared Kernel

El Shared Kernel funciona bajo reglas estrictas para evitar que se convierta en un problema:

  1. Mínimo necesario: Solo incluir lo que realmente se comparte entre contextos
  2. Propiedad compartida: Ambos equipos deben acordar cualquier cambio
  3. Versionado semántico: Cambios breaking requieren nueva versión major
  4. Tests compartidos: El kernel tiene sus propios tests que ambos equipos ejecutan
  5. 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

EstrategiaAcoplamientoAutonomíaCuándo usar
Shared KernelMedio (controlado)MediaConceptos idénticos en 2-3 contextos
ACL (Anti-Corruption Layer)BajoAltaModelos diferentes que necesitan traducción
Copiar y divergirNingunoTotalConceptos similares pero que evolucionan diferente
Servicio compartidoAltoBajaFuncionalidad 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íaOpciones
Distribuciónnpm packages, Maven artifacts, NuGet packages, Git submodules
VersionadoSemantic Versioning (SemVer), Conventional Commits
MonorepoNx, Turborepo, Lerna (kernel como paquete interno)
Registronpm registry privado, Artifactory, GitHub Packages
CI/CDPipeline 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.