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.
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 :
-
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.
-
Convention de nommage stricte — verbe au passe, sujet explicite :
OrderConfirmed,PaymentRefused. Pas de noms génériques. -
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.