Dans un système CQRS, le modèle d’écriture, les modèles de lecture et, le cas échéant, les événements d’Event Sourcing évoluent dans le temps ; il faut donc faire progresser le schéma et les contrats (commands, events, read models) sans casser les agrégats existants, les projections ni les consommateurs externes, en combinant versionning, migrations et reconstructions contrôlées.
Cet article fait partie de la série CQRS : 6 sur 6.
- Part 1 - Introduction à CQRS (cqrs 1/6)
- Part 2 - CQRS et le scaling horizontal (cqrs 2/6)
- Part 3 - Frontières transactionnelles et agrégats en CQRS (cqrs 3/6)
- Part 4 - Consistance éventuelle en pratique (cqrs 4/6)
- Part 5 - CQRS et Event Sourcing (cqrs 5/6)
- Part 6 - Cet article
L’évolution du schéma et le versionning dans un système CQRS (avec ou sans Event Sourcing)
1. Problème général
Un système CQRS vit longtemps, alors que :
- le modèle d’écriture évolue (nouvelles règles, nouveaux champs, refactorings) ;
- les modèles de lecture évoluent encore plus vite (nouveaux écrans, nouveaux reports, KPI, APIs) ;
- avec Event Sourcing, les événements anciens doivent rester lisibles par du code nouveau.
L’enjeu est de faire évoluer le schéma sans casser :
- les agrégats existants,
- les projections et rapports,
- les consommateurs externes (autres services, frontends, partenaires).
Évolution du modèle d’écriture (agrégats)
Côté écriture, quelques règles :
- Évolutions compatibles en priorité
- ajouter des champs optionnels (avec des valeurs par défaut côté code),
- éviter de renommer / supprimer des propriétés exposées publiquement,
- encapsuler les changements derrière des méthodes métier.
- Migration de données ciblée (sans ES)
- pour une base relationnelle classique, traiter l’évolution comme n’importe quelle migration :
- scripts SQL (ALTER TABLE, etc.),
- gestion de la compatibilité avec l’ancien code pendant une période de transition.
- pour une base relationnelle classique, traiter l’évolution comme n’importe quelle migration :
- Limiter les “breaking changes” sur les contrats externes
- les commandes et événements exposés ne doivent pas changer de manière brutale (voir point sur événements plus bas).
Évolution des modèles de lecture
Les modèles de lecture sont, par définition, des projections dérivées. Cela change leur stratégie d’évolution :
- On peut souvent se permettre de jeter et reconstruire un modèle de lecture :
- nouvelle table / index / vue,
- puis replay des événements (si ES) ou re-calcul depuis la base d’écriture.
- Approches courantes :
- nouvelle projection en parallèle :
- créer
OrderReadModelV2à côté deOrderReadModel, - alimenter les deux en parallèle,
- migrer progressivement les consommateurs vers V2, puis retirer V1.
- créer
- migration incrémentale :
- ajouter des colonnes, remplir progressivement via projections ou scripts de migration,
- adapter les handlers pour renseigner l’ancien et le nouveau schéma le temps de la transition.
- nouvelle projection en parallèle :
- Avantage de CQRS :
- le schéma de lecture peut être ajusté très librement, sans impacter directement le cœur métier d’écriture.
Versionning des événements (avec Event Sourcing)
Les événements sont particulièrement sensibles car ils constituent l’historique contractuel du système.
Problèmes typiques :
- Ancien code a produit
OrderCreatedavec certains champs ; - Nouveau code voudrait un
OrderCreatedplus riche ou différent.
Exemple simplifié :
- V1 :
OrderCreatedcontientOrderId,CustomerId. - V2 :
OrderCreatedajoute un champCurrency(monnaie de la commande).
Les projections et agrégats doivent alors être capables de :
- interpréter les anciens événements (sans
Currency) avec une valeur par défaut raisonnable ; - utiliser le champ
Currencydès qu’il est présent.
Stratégies de versionning :
- Éviter les breaking changes autant que possible
- ajouter des champs facultatifs, en gardant une sémantique compatible,
- ne pas changer la signification d’un champ existant.
- Événements versionnés
- introduire
OrderCreatedV2,OrderCreatedV3, etc. - conserver la prise en charge de plusieurs versions dans les projections / agrégats (au moins pendant une période).
- introduire
- Upcasters (conversion à la lecture)
- stocker les événements “tels quels” dans l’Event Store,
- à la lecture, transformer dynamiquement les anciens événements vers la forme attendue par le code actuel :
- ajout de champs par défaut,
- renommage logique,
- fusion / split de concepts.
- Adaptateurs pour consommateurs externes
- si des événements sont publiés vers l’extérieur, prévoir des endpoints de compatibilité ou des “translators” entre anciennes et nouvelles formes.
- compléter par des tests de contrat (consumer-driven contracts) pour garantir que les évolutions restent compatibles avec les attentes des consommateurs.
Stratégies de migration
Quelques patterns pratiques :
- Blue-green pour projections
- déployer une nouvelle version de projections qui écrit dans de nouveaux modèles de lecture,
- vérifier la cohérence (comparaison V1/V2),
- basculer les lectures vers V2,
- retirer V1 une fois stable.
- Rebuild à froid
- pour certaines vues : arrêter la projection, tout effacer, rejouer l’historique des événements pour reconstruire le modèle.
- Migration progressive
- maintenir temporairement :
- anciens schémas + anciens handlers,
- nouveaux schémas + nouveaux handlers,
- déplacer les consommateurs au fur et à mesure.
- maintenir temporairement :
- Feature flags / toggles
- contrôler au runtime quelle version de vue / API est utilisée, pour basculer sans redéploiement massif.
Sans Event Sourcing, ces stratégies s’appuient principalement sur des migrations de schéma classiques (scripts SQL) et, si nécessaire, sur des tâches de recalcul ou de rafraîchissement des vues de lecture à partir de la base d’écriture.
Bonnes pratiques générales
- Documenter les contrats (commands, events, read models publics) :
- formats, champs, sémantique, versions.
- Tests de compatibilité :
- rejouer d’anciens événements sur le nouveau code,
- vérifier que les projections / agrégats se reconstruisent correctement.
- Monitoring lors des migrations :
- surveiller erreurs de projection, deltas entre anciennes et nouvelles vues, temps de replay.
En résumé :
- Avec CQRS, l’évolution du schéma passe par :
- un versionning doux côté écriture (peu de ruptures),
- une grande liberté côté lecture (rebuild, V2 en parallèle),
- un soin particulier aux événements (versionnage, upcasting) si Event Sourcing est utilisé.
- L’objectif est de faire évoluer le système sans casser l’historique, ni les consommateurs existants, tout en tirant parti du fait que les vues de lecture sont dérivées et donc reconstruisibles.
Ce dernier volet s’appuie sur les notions d’agrégats, de consistance éventuelle et d’Event Sourcing présentées dans les parties précédentes de la série, afin de montrer comment faire vivre un système CQRS dans la durée sans perdre la maîtrise de son schéma et de ses contrats.