Aller au contenu

Event-driven

Communiquer par événements immuables — découplage temporel, audit natif et scalabilité reactive.


Le principe

L'architecture event-driven organisé la communication entre composants autour d'événements immuables. Un composant publie un événement (ce qui s'est passe), d'autres y souscrivent et réagissent. Le producteur ne sait pas qui consomme ses événements — et il ne doit pas le savoir.

Cette inversion est fondamentale. Dans une architecture synchrone, le service appelant connait le service appelé, attend sa réponse, et géré ses erreurs. Dans une architecture event-driven, le producteur publie un fait ("la commande a été confirmee") et continue. Les conséquences (envoyer un email, réserver le stock, mettre à jour le tableau de bord) sont gérées par des consommateurs indépendants.

graph LR
    subgraph Producers
        OS["Order Service"]
        PS["Payment Service"]
    end
    subgraph Bus["Event Bus"]
        E1["OrderCreated"]
        E2["PaymentProcessed"]
        E3["OrderShipped"]
    end
    subgraph Consumers
        NS["Notification Service"]
        RS["Reporting Service"]
        IS["Inventory Service"]
    end
    OS -->|"publie"| E1
    PS -->|"publie"| E2
    PS -->|"publie"| E3
    E1 --> NS
    E1 --> IS
    E2 --> NS
    E2 --> RS
    E3 --> NS

Types d'événements

Tous les événements ne jouent pas le même rôle. Distinguer trois catégories clarifie les choix d'implémentation.

Événements de domaine (Domain Events)

Un fait métier qui s'est produit dans un bounded context. Il porte la semantique du domaine.

{
  "type": "OrderConfirmed",
  "aggregateId": "order-42",
  "occurredAt": "2026-04-15T10:30:00Z",
  "payload": {
    "customerId": "cust-7",
    "items": [{"sku": "ABC-123", "qty": 2}],
    "totalAmount": 89.90
  }
}

Conventions de nommage : verbe au passe, sujet explicite. OrderConfirmed, PaymentRefused, InventoryReserved. Jamais DataUpdated ou RecordChanged — ces noms vagues sont le premier symptome de l'event soup.

Événements d'intégration (Intégration Events)

Un événement publie a destination d'autres bounded contexts ou systèmes externes. Il est souvent une version simplifiée ou traduite de l'événement de domaine, pour ne pas exposer les détails internes du contexte producteur.

Le contexte Orders publie un OrderConfirmed interne riche. L'événement d'intégration publie sur le bus externe peut être un OrderReadyForShipping avec uniquement les informations nécessaires au contexte Shipping.

Événements de notification

Un signal minimal qui indique qu'un changement s'est produit, sans porter les données. Le consommateur doit ensuite interroger le producteur pour obtenir les détails.

{
  "type": "OrderUpdated",
  "aggregateId": "order-42",
  "occurredAt": "2026-04-15T10:30:00Z"
}

Ce type est utile quand les données sont volumineuses ou quand le consommateur n'a pas besoin de toutes les informations à chaque fois. L'inconvénient : il crée un couplage temporel — le producteur doit être disponible au moment où le consommateur interroge.


Implémentations d'event bus

Le choix du broker impacte directement les garanties de livraison, la scalabilité et le modèle de consommation.

Broker Modèle Rétention Ordering Cas d'usage principal
Apache Kafka Log distribué, pull Configurable (jours/semaines) Par partition Streaming, audit trail, volume élevé
RabbitMQ Queue classique, push Jusqu'a consommation Par queue (FIFO) Task dispatch, RPC asynchrone
Google Pub/Sub Topic/subscription, push/pull 7 jours par défaut Par clé d'ordering Intégration cloud-native GCP
Amazon SNS/SQS Fan-out + queue 14 jours (SQS) FIFO optionnel Intégration cloud-native AWS
Redis Streams Log en mémoire Configurable Par stream Low-latency, volume modere

Kafka en détail

Kafka est le choix dominant pour les architectures event-driven a volume. Son modèle de log distribué offre une propriété unique : les événements sont conserves même après consommation. Un nouveau consommateur peut relire l'historique complet.

graph LR
    P1["Producer"] -->|"key: order-42"| T["Topic: orders"]
    T --> PA0["Partition 0"]
    T --> PA1["Partition 1"]
    T --> PA2["Partition 2"]
    PA0 --> CG1["Consumer Group A"]
    PA1 --> CG1
    PA2 --> CG1
    PA0 --> CG2["Consumer Group B"]
    PA1 --> CG2
    PA2 --> CG2

L'ordering est garanti par partition, pas globalement. Les événements avec la même clé (par exemple, le même orderId) atterrissent dans la même partition et sont donc traites dans l'ordre. Entre partitions différentes, aucune garantie d'ordre.

RabbitMQ en détail

RabbitMQ suit le modèle classique de message queuing. Les messages sont consommes et supprimes de la queue. Il excelle pour le dispatch de tâches (job queues) et la communication request-reply asynchrone.

La différence fondamentale avec Kafka : RabbitMQ est optimise pour la livraison rapide de messages individuels, Kafka pour le streaming de volumes massifs avec rétention.


Event sourcing

L'event sourcing pousse le modèle event-driven jusqu'à la couche de stockage. Au lieu de stocker l'état courant d'une entité (la ligne dans la base), on stocke la sequence complète des événements qui ont mene a cet état.

sequenceDiagram
    participant C as Commande
    participant ES as Event Store
    C->>ES: OrderCreated(items, customer)
    C->>ES: PaymentReceived(amount)
    C->>ES: ItemShipped(trackingId)
    Note over ES: L'etat courant = replay<br/>des 3 evenements

L'état courant se reconstruit en rejouant les événements depuis le début. Pour éviter de rejouer des milliers d'événements à chaque lecture, on utilise des snapshots périodiques.

Avantages :

  • Audit trail complet et natif — chaque changement est trace
  • Possibilite de reconstruire l'état a n'importe quel point dans le temps (time travel)
  • Les projections de lecture peuvent être recalculees en rejouant les événements
  • Debuggabilite supérieure — on voit exactement ce qui s'est passe

Inconvénients :

  • Complexité du modèle — les développeurs doivent penser en événements, pas en CRUD
  • Versionning des événements — un événement publie il y a 2 ans doit toujours être comprehensible
  • Performance de reconstruction sans snapshots
  • Courbe d'apprentissage significative

Event sourcing n'est pas obligatoire

L'architecture event-driven ne requiert pas l'event sourcing. On peut publier des événements sur un bus sans stocker l'état sous forme d'événements. L'event sourcing est un pattern de stockage, pas un pattern de communication.


Cohérence eventuelle (Eventual Consistency)

Dans une architecture event-driven, la cohérence des données entre services n'est pas immédiate. Quand le service Orders publie OrderConfirmed, le service Inventory ne met pas à jour son stock instantanément — il le fait quand il reçoit et traite l'événement, ce qui peut prendre quelques millisecondes ou quelques secondes.

Cette cohérence eventuelle a des conséquences concrètes :

  • Un utilisateur peut confirmer une commande et voir un stock pas encore mis à jour
  • Deux requêtes successives peuvent retourner des états incoherents
  • Les rapports temps réel ne sont pas vraiment temps réel

Comment gérer la cohérence eventuelle :

Stratégie Description
Read-your-writes Après une écriture, lire depuis le producteur, pas depuis une projection
UI optimiste Montrer le résultat attendu immédiatement, corriger si l'événement échoué
Correlation ID Tracer une opération de bout en bout pour savoir ou elle en est
Timeout + compensation Si l'événement n'est pas traite dans un délai, déclencher une action corrective

Garanties de livraison

Les garanties de livraison déterminent le comportement en cas de panne. Elles doivent être définies explicitement pour chaque flux d'événements.

Garantie Ce qu'elle implique
At-least-once L'événement sera livre au moins une fois ; les consumers doivent être idempotents
At-most-once L'événement peut être perdu mais ne sera pas duplique ; acceptable pour des métriques
Exactly-once Difficile a implémenter, coût élevé ; Kafka transactions et sagas
Ordering Garanti par partition Kafka, mais pas entre partitions ; a documenter par contexte

L'idempotence des consumers est la contrainte la plus importante. Un consumer qui reçoit deux fois PaymentProcessed ne doit pas debiter le client deux fois. Stratégies : clé d'idempotence stockee en base, deduplication par eventId, opérations naturellement idempotentes (set vs increment).


Le piege : l'event soup

Sans gouvernance, le nombre d'événements explose et leur semantique devient floue. Symptomes :

  • Événements avec des noms vagues (DataUpdated, RecordChanged)
  • Pas de schéma registry — un consumer ne sait pas ce qu'il va recevoir
  • Ordre des événements non garanti et non documente
  • Pas de dead letter queue — les événements en échec disparaissent silencieusement
  • Personne ne sait combien de consumers un événement a

La solution passe par trois piliers :

  1. Schéma registry — Avro, Protobuf ou JSON Schéma. Chaque événement a un schéma versionne. Un producteur ne peut pas publier un événement qui ne respecte pas son schéma.

  2. Convention de nommage stricte — verbe au passe, sujet explicite : OrderConfirmed, PaymentRefused. Pas de noms génériques.

  3. Catalogue d'événements — un document (ou un outil) qui liste tous les événements, leurs producteurs, leurs consumers, leur schéma et leur garantie de livraison.

Event-driven complementaire, pas exclusif

L'event-driven s'utilise souvent en complement d'une autre topologie. Un monolithe modulaire peut publier des événements vers un bus externe pour intégrer des systèmes tiers. Des microservices peuvent communiquer en synchrone pour les lectures et en asynchrone pour les side effects.


Chapitre suivant : Serverless — fonctions à la demande, modèle de coût et limites opérationnelles.