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égration | Logique métier propre à un seul contexte |
| Interfaces de contrats (API contracts) | Modèles de persistance (entités de BD) |
| Validations communes | Configuration 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 :
- Minimum nécessaire : n’inclure que ce qui est réellement partagé entre les contextes
- Propriété partagée : les deux équipes doivent se mettre d’accord sur tout changement
- Versionnage sémantique : les changements cassants nécessitent une nouvelle version majeure
- Tests partagés : le kernel possède ses propres tests, exécutés par les deux équipes
- 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égie | Couplage | Autonomie | Quand l’utiliser |
|---|---|---|---|
| Shared Kernel | Moyen (maîtrisé) | Moyenne | Concepts identiques dans 2-3 contextes |
| ACL (Anti-Corruption Layer) | Faible | Élevée | Modèles différents nécessitant une traduction |
| Copier et diverger | Aucun | Totale | Concepts similaires mais évoluant différemment |
| Service partagé | Élevé | Faible | Fonctionnalité 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égorie | Options |
|---|---|
| Distribution | npm packages, Maven artifacts, NuGet packages, Git submodules |
| Versionnage | Semantic Versioning (SemVer), Conventional Commits |
| Monorepo | Nx, Turborepo, Lerna (kernel comme paquet interne) |
| Registre | npm registry privé, Artifactory, GitHub Packages |
| CI/CD | Pipeline 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.