Shared Kernel

How to share a controlled subset of the domain model across bounded contexts to reduce duplication without sacrificing team autonomy.

What problem it solves

In a microservices architecture with multiple bounded contexts, each context has its own domain model. But sometimes two or more contexts need to share certain concepts that are identical on both sides:

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

The problems are:

  • Duplication: The same code is maintained in multiple places
  • Divergence: Over time, the copies evolve differently and become inconsistent
  • Synchronization effort: Every change must be manually replicated across all contexts
  • Subtle bugs: A validation updated in one service but not another causes inconsistencies

How it works

The Shared Kernel defines a small, explicit subset of the domain model that is shared between two or more bounded contexts. This shared kernel is managed as an independent library or package with its own versioning.

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)

What to include in the Shared Kernel

IncludeDo not include
Shared Value Objects (Address, Money, DateRange)Entities with context-specific business logic
Shared status enums (OrderStatus)Repositories or application services
Integration event DTOsBusiness logic belonging to a single context
Contract interfaces (API contracts)Persistence models (DB entities)
Common validationsInfrastructure-specific configuration

Rules of the Shared Kernel

The Shared Kernel operates under strict rules to keep it from becoming a problem:

  1. Minimum necessary: Only include what is truly shared between contexts
  2. Shared ownership: Both teams must agree on any change
  3. Semantic versioning: Breaking changes require a new major version
  4. Shared tests: The kernel has its own tests that both teams run
  5. Joint review: Changes to the kernel require approval from all consuming teams

Example: Shared Kernel as a package

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

Each service consumes it as a dependency:

// Order Service - package.json
{
  "dependencies": {
    "@company/shared-kernel": "^2.1.0"
  }
}

// Shipping Service - package.json
{
  "dependencies": {
    "@company/shared-kernel": "^2.1.0"
  }
}

Evolution of the 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 other strategies

StrategyCouplingAutonomyWhen to use
Shared KernelMedium (controlled)MediumIdentical concepts across 2-3 contexts
ACL (Anti-Corruption Layer)LowHighDifferent models that need translation
Copy and divergeNoneFullSimilar concepts that evolve differently
Shared serviceHighLowComplete shared functionality

Advantages

  • Eliminates duplication: A single place for shared code
  • Guaranteed consistency: All contexts use the same version of the model
  • Coordinated evolution: Changes propagate in a controlled way
  • Centralized testing: The kernel’s tests validate shared behavior
  • Explicit contracts: The kernel formally defines what is shared

Trade-offs / Disadvantages

  • Coupling between teams: Changes to the kernel require coordination between teams
  • Development speed: A team can get blocked waiting on a kernel change
  • Risk of growth: If the kernel grows too large, it becomes a shared monolith
  • Complex versioning: Managing versions and compatibility across consumers requires discipline
  • Coordinated deployment: Breaking changes require all consumers to update
  • Governance: Needs clear rules about who can modify the kernel and how

When to use

  • Two or more bounded contexts share identical value objects (Address, Money, DateRange)
  • Integration events that must have the same schema on both producer and consumer
  • Status enums that must remain consistent across services
  • When duplication causes frequent bugs due to divergence
  • Teams working in closely related domains that can coordinate easily

When to avoid

  • More than 3-4 consuming contexts (coordination becomes unmanageable)
  • When the β€œshared” concepts actually have different semantics in each context
  • Geographically distributed teams with limited communication
  • When team autonomy matters more than avoiding duplication
  • If the kernel tends to grow unchecked (a sign that the bounded contexts are poorly defined)

Common technologies and implementations

CategoryOptions
Distributionnpm packages, Maven artifacts, NuGet packages, Git submodules
VersioningSemantic Versioning (SemVer), Conventional Commits
MonorepoNx, Turborepo, Lerna (kernel as an internal package)
RegistryPrivate npm registry, Artifactory, GitHub Packages
CI/CDDedicated pipeline for the kernel with compatibility tests

Relationship to other patterns

  • DDD (Bounded Context): The Shared Kernel is a relationship between bounded contexts defined by Eric Evans
  • Anti-Corruption Layer: An alternative to the Shared Kernel when the models differ
  • Event-Driven Architecture: Integration event schemas are natural candidates for the kernel
  • Microservices: The Shared Kernel must stay small so it doesn’t compromise service independence
  • Contracts (API Contracts): API contracts can live in the Shared Kernel

Next steps

The Shared Kernel is a powerful tool, but it demands discipline. To understand the alternative when models are incompatible, check out Anti-Corruption Layer. To see how integration events benefit from a shared kernel, explore Event-Driven Architecture.