Aller au contenu

Modèle global d'événements

Statut

Spécification du cœur de plateforme (M3).

Pourquoi ce document existe

Cette page décrit le modèle global d'événements de CMonChoix.

Un Domain Event est un fait métier qui vient réellement de se produire. Il ne s'agit ni d'une commande à exécuter, ni d'une requête de lecture, ni d'un simple message de log.

Exemples :

  • une identité produit vient d'être établie ;
  • un conflit vient d'être détecté ;
  • une variante vient d'être rattachée à un produit.

L'intérêt d'un événement est de permettre aux autres composants de réagir sans que le domaine métier ait besoin de les connaître directement.

Vue simplifiée

Product Domain
    │ émet des événements
    ▼
Runtime
    │ transporte / orchestre
    ▼
Projection Engine
    │ reconstruit les vues de lecture
    ▼
Catalog Domain
    │ expose les représentations dérivées

Le sens du flux est important : on ne fait pas remonter une décision du Catalog ou du Frontend pour réécrire silencieusement la vérité du Product Domain.

Nature d'un événement

Un événement doit être :

  • immuable : une fois émis, il n'est pas modifié ;
  • append-only : on ajoute de nouveaux événements plutôt que de réécrire l'historique ;
  • ordonné logiquement au moins dans le périmètre de son agrégat ;
  • rejouable : il peut être relu pour reconstruire un état ;
  • versionné : sa structure peut évoluer sans casser les anciens événements.

Un événement n'est pas :

  • une commande ;
  • une Query ;
  • un snapshot complet de l'état ;
  • une règle métier exécutable.

Pourquoi séparer fait et décision

Une règle métier décide qu'un conflit existe. L'événement ProductConflictDetected enregistre le fait que cette décision a eu lieu.

L'événement ne doit pas lui-même contenir l'algorithme qui détecte le conflit.

Cette séparation permet de comprendre l'historique sans dupliquer la logique métier dans le transport ou le stockage.

Catégories principales

Événements Product

Ils décrivent des changements de la vérité métier produit, par exemple :

  • ProductCreated ;
  • ProductUpdated ;
  • ProductIdentityEstablished ;
  • ProductVariantChanged ;
  • ProductObservationAdded ;
  • ProductConflictDetected ;
  • ProductConflictResolved.

Événements Catalog

Ils décrivent les représentations dérivées du catalogue, par exemple :

  • CatalogItemCreated ;
  • CatalogItemUpdated ;
  • CatalogViewGenerated ;
  • CatalogCollectionUpdated.

Événements Runtime

Ils décrivent l'exécution technique de certains traitements, par exemple :

  • ProjectionTriggered ;
  • ProjectionRebuilt ;
  • EventReplayStarted ;
  • EventReplayCompleted.

Il faut garder ces catégories distinctes : un événement technique n'est pas automatiquement un fait métier.

Structure minimale

Chaque événement doit au minimum pouvoir fournir :

  • event_id : identifiant unique, généralement UUID ;
  • event_type : type de l'événement ;
  • aggregate_id : identifiant de l'agrégat concerné ;
  • aggregate_type : type d'agrégat ;
  • timestamp : moment de l'événement ;
  • version : version du schéma de l'événement ;
  • payload : données nécessaires pour décrire le fait.

Versionnement

Les anciens événements doivent rester lisibles et rejouables.

On fait donc évoluer leur schéma par version :

v1 → structure initiale
v2 → évolution compatible ou migration explicitement gérée

On ne modifie pas rétroactivement un événement historique juste parce qu'un nouveau champ apparaît aujourd'hui.

Ordre des événements

L'ordre doit être cohérent pour un même agrégat.

Exemple : pour un ProductId, l'identité ne doit pas être rejouée après un événement qui dépend déjà de sa nouvelle version si cela inverse la chronologie métier.

En revanche, un ordre global absolu entre tous les produits n'est pas nécessaire.

Les consommateurs doivent donc tolérer certains retards ou arrivées désordonnées entre agrégats différents.

Livraison au moins une fois

Le modèle suppose une sémantique at-least-once : un même événement peut être reçu plus d'une fois.

Cela implique que les consommateurs doivent être idempotents : retraiter le même événement ne doit pas créer un doublon ou une seconde mutation incorrecte.

Replay

Le système doit pouvoir rejouer :

  • tous les événements ;
  • les événements d'un seul ProductId ;
  • une fenêtre temporelle précise.

Le replay sert notamment à reconstruire des projections, vérifier une migration ou diagnostiquer une divergence.

Cohérence

À l'intérieur d'un agrégat Product, les invariants doivent rester immédiatement vrais.

Entre domaines différents, la plateforme accepte une cohérence éventuelle : une projection peut avoir quelques instants de retard avant de refléter un nouvel événement.

Cela ne signifie pas qu'une divergence permanente est acceptable.

Règle critique

Un événement décrit un fait. Il ne contient pas la logique qui décide de ce fait.

Si tu trouves dans un handler d'événement une règle qui décide soudainement de l'identité canonique d'un produit, la responsabilité est probablement au mauvais endroit.

Diagnostic pour un nouveau mainteneur

Si une projection ne reflète pas un changement récent :

  1. vérifie que le domaine a réellement produit la décision ;
  2. vérifie que l'événement correspondant existe ;
  3. vérifie que le Runtime l'a transporté ou traité ;
  4. vérifie que le consommateur l'a appliqué ;
  5. vérifie si un replay ou une reconstruction remet la projection en cohérence.

Ne corrige pas la projection à la main avant d'avoir compris à quelle étape le flux s'est interrompu.

À lire ensuite