Shared Kernel

Comment partager un sous-ensemble maîtrisé du modèle de domaine entre plusieurs bounded contexts pour réduire la duplication sans sacrifier l'autonomie des équipes.

Quel problème résout-il

Dans une architecture de microservices comportant plusieurs bounded contexts, chaque contexte possède son propre modèle de domaine. Mais parfois, deux contextes ou plus doivent partager certains concepts qui sont identiques des deux côtés :

Sans Shared Kernel :
  Order Service                    Shipping Service
  ┌────────────────┐              ┌────────────────┐
  │ Address {      │              │ Address {      │
  │   street       │              │   street       │
  │   city         │              │   city         │
  │   state        │              │   state        │
  │   zipCode      │              │   zipCode      │
  │   country      │              │   country      │
  │   validate()   │              │   validate()   │
  │ }              │              │ }              │
  └────────────────┘              └────────────────┘
  
  Même classe dupliquée dans les deux services
  Si la validation change, il faut la modifier des deux côtés
  Risque de divergence silencieuse

Les problèmes sont les suivants :

  • Duplication : le même code est maintenu à plusieurs endroits
  • Divergence : avec le temps, les copies évoluent différemment et deviennent incohérentes
  • Effort de synchronisation : chaque changement doit être répliqué manuellement dans tous les contextes
  • Bugs subtils : une validation mise à jour dans un service mais pas dans un autre provoque des incohérences

Comment ça fonctionne

Le Shared Kernel définit un sous-ensemble petit et explicite du modèle de domaine partagé entre deux bounded contexts ou plus. Ce noyau partagé est géré comme une librairie ou un paquet indépendant, avec son propre versionnage.

Avec Shared Kernel :
  ┌─────────────────────────────────┐
  │        Shared Kernel            │
  │  (librairie versionnée)         │
  │                                 │
  │  Address { street, city, ... }  │
  │  Money { amount, currency }     │
  │  DateRange { from, to }         │
  │  OrderStatus enum               │
  └─────────┬───────────┬───────────┘
            │           │
            ▼           ▼
  Order Service    Shipping Service
  (utilise v2.1.0) (utilise v2.1.0)

Que faut-il inclure dans le Shared Kernel

À inclureÀ ne pas inclure
Value Objects partagés (Address, Money, DateRange)Entités avec logique métier spécifique
Enums d’état partagés (OrderStatus)Repositories ou services applicatifs
DTOs des événements d’intégrationLogique métier propre à un seul contexte
Interfaces de contrats (API contracts)Modèles de persistance (entités de BD)
Validations communesConfiguration spécifique à l’infrastructure

Règles du Shared Kernel

Le Shared Kernel fonctionne selon des règles strictes pour éviter qu’il ne devienne un problème :

  1. Minimum nécessaire : n’inclure que ce qui est réellement partagé entre les contextes
  2. Propriété partagée : les deux équipes doivent se mettre d’accord sur tout changement
  3. Versionnage sémantique : les changements cassants nécessitent une nouvelle version majeure
  4. Tests partagés : le kernel possède ses propres tests, exécutés par les deux équipes
  5. Revue conjointe : les changements au kernel nécessitent l’approbation de toutes les équipes consommatrices

Exemple : Shared Kernel comme paquet

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

Chaque service le consomme comme une dépendance :

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

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

Évolution du Shared Kernel

Version 1.0.0 : Address, Money
Version 1.1.0 : + DateRange (rétrocompatible)
Version 2.0.0 : Address change la validation du zipCode (breaking change)
  → Les deux équipes doivent mettre à jour et tester
  → Le déploiement est coordonné

Shared Kernel face aux autres stratégies

StratégieCouplageAutonomieQuand l’utiliser
Shared KernelMoyen (maîtrisé)MoyenneConcepts identiques dans 2-3 contextes
ACL (Anti-Corruption Layer)FaibleÉlevéeModèles différents nécessitant une traduction
Copier et divergerAucunTotaleConcepts similaires mais évoluant différemment
Service partagéÉlevéFaibleFonctionnalité complète partagée

Avantages

  • Élimine la duplication : un seul endroit pour le code partagé
  • Cohérence garantie : tous les contextes utilisent la même version du modèle
  • Évolution coordonnée : les changements se propagent de façon maîtrisée
  • Tests centralisés : les tests du kernel valident le comportement partagé
  • Contrats explicites : le kernel définit formellement ce qui est partagé

Compromis / Inconvénients

  • Couplage entre équipes : les changements au kernel nécessitent une coordination entre équipes
  • Vitesse de développement : une équipe peut se retrouver bloquée en attendant un changement dans le kernel
  • Risque de croissance : si le kernel grossit trop, il devient un monolithe partagé
  • Versionnage complexe : gérer les versions et la compatibilité entre consommateurs demande de la discipline
  • Déploiement coordonné : les changements cassants exigent que tous les consommateurs se mettent à jour
  • Gouvernance : des règles claires sont nécessaires pour savoir qui peut modifier le kernel et comment

Quand l’utiliser

  • Deux bounded contexts ou plus partagent des value objects identiques (Address, Money, DateRange)
  • Des événements d’intégration doivent avoir le même schéma côté producteur et côté consommateur
  • Des enums d’état doivent rester cohérents entre les services
  • La duplication provoque des bugs fréquents liés à la divergence
  • Les équipes travaillent sur des domaines proches et peuvent se coordonner facilement

Quand l’éviter

  • Plus de 3-4 contextes consommateurs (la coordination devient ingérable)
  • Lorsque les concepts « partagés » ont en réalité une sémantique différente selon le contexte
  • Équipes réparties géographiquement avec peu de communication
  • Lorsque l’autonomie des équipes est plus importante que d’éviter la duplication
  • Si le kernel tend à croître de façon incontrôlée (signe que les bounded contexts sont mal définis)

Technologies et implémentations courantes

CatégorieOptions
Distributionnpm packages, Maven artifacts, NuGet packages, Git submodules
VersionnageSemantic Versioning (SemVer), Conventional Commits
MonorepoNx, Turborepo, Lerna (kernel comme paquet interne)
Registrenpm registry privé, Artifactory, GitHub Packages
CI/CDPipeline dédié au kernel avec tests de compatibilité

Relation avec d’autres patterns

  • DDD (Bounded Context) : le Shared Kernel est une relation entre bounded contexts définie par Eric Evans
  • Anti-Corruption Layer : alternative au Shared Kernel lorsque les modèles diffèrent
  • Event-Driven Architecture : les schémas des événements d’intégration sont des candidats naturels pour le kernel
  • Microservices : le Shared Kernel doit rester petit pour ne pas compromettre l’indépendance des services
  • Contrats (API Contracts) : les contrats d’API peuvent vivre dans le Shared Kernel

Prochaines étapes

Le Shared Kernel est un outil puissant, mais qui exige de la discipline. Pour comprendre l’alternative lorsque les modèles sont incompatibles, consultez l’Anti-Corruption Layer. Pour voir comment les événements d’intégration tirent parti du noyau partagé, explorez l’Event-Driven Architecture.