Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Shared Kernel Pattern

Introduction to the Shared Kernel Pattern

The Shared Kernel Pattern is a domain-driven design (DDD) approach where two or more tightly integrated Bounded Contexts share a common model, library, or subset of domain logic, referred to as the Shared Kernel. This shared component reduces duplication and ensures consistency for critical domain elements that are closely related across contexts. The pattern is used when complete separation of contexts is impractical due to tight coupling, but it requires careful coordination to avoid unintended dependencies.

For example, in an e-commerce system, the Order Context and Payment Context might share a Shared Kernel containing common domain models like Currency or Money, ensuring consistent handling of monetary values across both contexts.

The Shared Kernel Pattern enables tightly integrated bounded contexts to share a common model or library, reducing duplication while maintaining consistency.

Shared Kernel Pattern Diagram

The diagram illustrates the Shared Kernel Pattern. A Client interacts with two Bounded Contexts (e.g., Context A, Context B), each accessing a Shared Kernel for common domain logic. The contexts may also interact directly via Context Interactions. Arrows are color-coded: yellow (dashed) for requests, blue (dotted) for shared kernel access, and red (dashed) for context interactions.

graph TD A[Client] -->|Request| B[Context A] A -->|Request| C[Context B] B -->|Shared Kernel Access| D[Shared Kernel] C -->|Shared Kernel Access| D B -->|Context Interaction| C subgraph Bounded Contexts B C D end style A stroke:#ff6f61,stroke-width:2px style B stroke:#ffeb3b,stroke-width:2px style C stroke:#ffeb3b,stroke-width:2px style D stroke:#405de6,stroke-width:2px linkStyle 0 stroke:#ffeb3b,stroke-width:2px,stroke-dasharray:5,5 linkStyle 1 stroke:#ffeb3b,stroke-width:2px,stroke-dasharray:5,5 linkStyle 2 stroke:#405de6,stroke-width:2px,stroke-dasharray:2,2 linkStyle 3 stroke:#405de6,stroke-width:2px,stroke-dasharray:2,2 linkStyle 4 stroke:#ff4d4f,stroke-width:2px,stroke-dasharray:3,3
The Shared Kernel provides a common model accessed by multiple Bounded Contexts, ensuring consistency for tightly integrated domain logic.

Key Components

The core components of the Shared Kernel Pattern include:

  • Bounded Context: A specific boundary within which a domain model is defined and applicable, representing a microservice or module.
  • Shared Kernel: A shared subset of the domain model, library, or code (e.g., entities, value objects, utilities) used by multiple bounded contexts.
  • Domain Model: The common objects, rules, or logic within the shared kernel, ensuring consistent behavior across contexts.
  • Context Interaction: Communication between bounded contexts, often via APIs, events, or direct calls, to coordinate beyond the shared kernel.
  • Governance: Agreements and processes between teams to manage the shared kernel’s evolution, ensuring alignment and avoiding conflicts.
  • Versioning: Mechanisms to handle changes to the shared kernel, maintaining compatibility across contexts.

The Shared Kernel Pattern is typically applied in domain-driven design within microservices or modular monoliths where certain domain elements require tight integration.

Benefits of the Shared Kernel Pattern

The Shared Kernel Pattern offers several advantages for tightly integrated systems:

  • Reduced Duplication: Eliminates redundant code or models by sharing common domain logic across contexts.
  • Consistency: Ensures uniform behavior and data representation for shared domain elements, reducing errors.
  • Efficiency: Streamlines development by reusing a single, well-tested shared kernel rather than replicating logic.
  • Tight Integration: Facilitates collaboration between closely related bounded contexts, improving system cohesion.
  • Simplified Maintenance: Centralizes updates to shared logic, reducing the effort to propagate changes across contexts.
  • Alignment: Encourages teams to align on critical domain concepts, fostering better communication.

These benefits make the Shared Kernel Pattern suitable for systems where certain domain elements are tightly coupled, such as financial systems, e-commerce platforms, or enterprise applications.

Implementation Considerations

Implementing the Shared Kernel Pattern requires careful coordination to balance integration benefits with potential coupling risks. Key considerations include:

  • Scope Definition: Keep the shared kernel minimal, including only essential models or utilities to avoid unnecessary coupling.
  • Team Coordination: Establish clear governance with all teams using the shared kernel to agree on changes, avoiding unilateral modifications.
  • Versioning Strategy: Use semantic versioning and backward compatibility to manage shared kernel updates without breaking dependent contexts.
  • Testing: Implement comprehensive tests for the shared kernel, ensuring changes do not introduce regressions across contexts.
  • Dependency Management: Package the shared kernel as a library or module (e.g., npm package, Maven artifact) for easy integration and updates.
  • Performance Impact: Optimize shared kernel code to minimize performance overhead, especially for frequently accessed models.
  • Security: Secure shared kernel access, ensuring only authorized contexts can use or modify it.
  • Monitoring: Instrument the shared kernel with metrics and logs, integrating with tools like Prometheus or OpenTelemetry to track usage and issues.
  • Evolution Path: Plan for refactoring the shared kernel if contexts diverge over time, potentially splitting it into separate models.
  • Documentation: Maintain clear documentation for the shared kernel, detailing its models, usage, and governance processes.

Common tools and frameworks for implementing the Shared Kernel Pattern include:

  • Package Managers: npm, Maven, or NuGet for distributing the shared kernel as a library.
  • Version Control: Git with monorepos or dedicated repositories for managing shared kernel code.
  • CI/CD: Jenkins, GitHub Actions, or CircleCI for automated testing and deployment of the shared kernel.
  • DDD Frameworks: Spring Boot, NestJS, or Axon for implementing domain-driven design patterns.
  • Testing Tools: Jest, JUnit, or Mocha for ensuring shared kernel reliability.
The Shared Kernel Pattern is ideal for tightly integrated bounded contexts, but requires careful governance to manage coupling and ensure maintainability.

Example: Shared Kernel Pattern in Action

Below is a detailed example demonstrating the Shared Kernel Pattern using two Node.js microservices: an Order Service and a Payment Service, both sharing a Shared Kernel library for handling monetary values. The shared kernel is distributed as an npm package, ensuring consistency in currency and money calculations.

# Project Structure /shared-kernel /src money.js package.json /order-service /src index.js package.json Dockerfile /payment-service /src index.js package.json Dockerfile docker-compose.yml # shared-kernel/package.json { "name": "@ecommerce/shared-kernel", "version": "1.0.0", "main": "src/money.js", "private": true } # shared-kernel/src/money.js class Money { constructor(amount, currency) { if (!Number.isFinite(amount) || amount < 0) { throw new Error('Invalid amount'); } if (!['USD', 'EUR'].includes(currency)) { throw new Error('Invalid currency'); } this.amount = amount; this.currency = currency; } add(other) { if (this.currency !== other.currency) { throw new Error('Currency mismatch'); } return new Money(this.amount + other.amount, this.currency); } subtract(other) { if (this.currency !== other.currency) { throw new Error('Currency mismatch'); } if (this.amount < other.amount) { throw new Error('Insufficient funds'); } return new Money(this.amount - other.amount, this.currency); } toString() { return \`\${this.amount.toFixed(2)} \${this.currency}\`; } } module.exports = { Money }; # order-service/package.json { "name": "order-service", "version": "1.0.0", "dependencies": { "express": "^4.17.1", "axios": "^0.27.2", "@ecommerce/shared-kernel": "file:../shared-kernel" } } # order-service/Dockerfile FROM node:16 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3001 CMD ["node", "src/index.js"] # order-service/src/index.js const express = require('express'); const axios = require('axios'); const { Money } = require('@ecommerce/shared-kernel'); const app = express(); app.use(express.json()); app.post('/orders', async (req, res) => { const { item_id, quantity, unit_price, currency } = req.body; try { // Calculate total using Shared Kernel const unitPrice = new Money(unit_price, currency); const totalPrice = new Money(unit_price * quantity, currency); // Check payment via Payment Service const paymentResponse = await axios.post('http://payment-service:3002/payments', { amount: totalPrice.amount, currency: totalPrice.currency }); res.status(201).json({ order_id: 'order123', total: totalPrice.toString(), payment_id: paymentResponse.data.payment_id }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(3001, () => console.log('Order Service running on port 3001')); # payment-service/package.json { "name": "payment-service", "version": "1.0.0", "dependencies": { "express": "^4.17.1", "@ecommerce/shared-kernel": "file:../shared-kernel" } } # payment-service/Dockerfile FROM node:16 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3002 CMD ["node", "src/index.js"] # payment-service/src/index.js const express = require('express'); const { Money } = require('@ecommerce/shared-kernel'); const app = express(); app.use(express.json()); app.post('/payments', (req, res) => { const { amount, currency } = req.body; try { // Validate payment using Shared Kernel const paymentAmount = new Money(amount, currency); // Simulate payment processing res.status(201).json({ payment_id: 'payment456', amount: paymentAmount.toString(), status: 'processed' }); } catch (error) { res.status(400).json({ error: error.message }); } }); app.listen(3002, () => console.log('Payment Service running on port 3002')); # docker-compose.yml version: '3.8' services: order-service: build: ./order-service ports: - "3001:3001" volumes: - ./shared-kernel:/shared-kernel depends_on: - payment-service payment-service: build: ./payment-service ports: - "3002:3002" volumes: - ./shared-kernel:/shared-kernel

This example demonstrates the Shared Kernel Pattern with two microservices:

  • Shared Kernel: A library (@ecommerce/shared-kernel) containing the Money class, which handles monetary calculations with validation for amount and currency.
  • Order Service: Creates orders, calculates totals using the Money class, and interacts with the Payment Service via HTTP to process payments.
  • Payment Service: Processes payments, validating amounts with the Money class from the shared kernel.
  • Context Interaction: The Order Service calls the Payment Service’s API, demonstrating integration beyond the shared kernel.
  • Docker Compose: Orchestrates the services, mounting the shared kernel as a local volume for development simplicity.

To run this example, create the directory structure, save the files, and execute:

docker-compose up --build

Test the Order Service by creating an order:

curl -X POST http://localhost:3001/orders -H "Content-Type: application/json" -d '{"item_id":"item1","quantity":2,"unit_price":50.00,"currency":"USD"}'

This setup illustrates the Shared Kernel Pattern’s principles: the Money class is shared between the Order and Payment contexts, ensuring consistent monetary handling. The shared kernel is distributed as a library, and the services communicate via APIs for additional coordination. In a production environment, the shared kernel would be published to a private npm registry, with versioning and governance processes to manage updates. Monitoring and testing would ensure the shared kernel’s reliability across contexts.