Riadh Mnasri
← Retour au blog
3 min de lecture

Event-driven avec Kafka : ce qu'on gagne, et ce qu'on perd

Sur MissionMatch, le streaming d'événements Kafka découple la publication d'une mission de tous les traitements qui doivent en découler : notification, indexation, statistiques. Le bénéfice de découplage est réel. Ce qu'on perd en échange l'est tout autant, et c'est rarement mis en avant avec la même clarté.

Le problème que Kafka résout vraiment

Sans event streaming, chaque nouveau traitement déclenché par un événement métier (une mission publiée, par exemple) demande de modifier le service qui publie cet événement, pour y ajouter un appel direct. Au bout de quelques traitements, ce service connaît tous ses consommateurs, ce qui va exactement à l'encontre du principe de responsabilité unique. Publier un événement sur un topic, et laisser chaque consommateur s'abonner indépendamment, inverse cette dépendance : le service producteur ignore qui consomme, et combien.

                          topic: mission.published
                    ┌────────────────────────────────┐
Mission Service ──▶ │  ████  ████  ████  ████  ████  │
 (producteur)       └────────────────────────────────┘
                        │           │           │
                        ▼           ▼           ▼
                 Notification   Indexation   Statistiques
                  (consumer)    (consumer)    (consumer)

Chaque consommateur avance à son propre rythme, se rejoue en cas d'échec indépendamment des autres, et peut être ajouté ou retiré sans toucher au service producteur ni aux autres consommateurs.

Ce que ce découplage fait perdre

Le prix de cette flexibilité, c'est la perte de garanties qu'on tenait pour acquises avec un appel synchrone direct. Un appel de fonction classique échoue de façon visible et immédiate. Un message Kafka peut être traité en retard, être dupliqué (au moins une fois, pas exactement une fois, sauf configuration spécifique), ou être traité dans le désordre selon le partitionnement choisi. Ces propriétés ne sont pas des bugs : elles font partie du contrat du système, mais elles doivent être conçues explicitement, pas découvertes en production.

Ce qui change concrètement, propriété par propriété

PropriétéAppel synchrone directÉvénement Kafka
LivraisonGarantie ou exception immédiateAu moins une fois (le consommateur doit gérer les doublons)
OrdreOrdre d'exécution du code appelantGaranti par partition, pas globalement
Latence perçueImmédiate, bloquanteAsynchrone, le producteur ne connaît pas le résultat du traitement
Couplage au démarrageLe service appelé doit être disponibleLe consommateur peut être arrêté, il rattrape à son retour

Chaque ligne de ce tableau est un compromis explicite, pas un défaut caché du système : accepter « au moins une fois » plutôt que « exactement une fois » est un choix de conception qui déplace la responsabilité de l'idempotence vers le consommateur, pas un oubli d'implémentation.

La question qui détermine si Kafka est justifié

Avant d'introduire Kafka sur un projet, la question à se poser n'est pas « est-ce que ça peut être utile », mais « est-ce que j'ai vraiment plusieurs consommateurs indépendants qui doivent réagir au même événement, avec des rythmes de traitement différents ». Si la réponse est un seul consommateur synchrone, un appel de fonction classique reste plus simple, plus facile à tester, et plus facile à faire évoluer. Kafka se justifie quand le découplage résout un vrai problème d'organisation ou d'échelle, pas comme choix par défaut d'architecture moderne.

Ce que ça implique pour les tests

Sur MissionMatch, chaque consommateur d'événement est testé indépendamment du producteur, avec un événement construit directement en mémoire plutôt qu'en passant par un vrai broker Kafka dans les tests unitaires. C'est la même discipline hexagonale qui s'applique ici : le domaine ne doit pas savoir qu'il communique via Kafka, seulement qu'il publie un événement à travers une interface. Le broker concret reste un détail d'infrastructure, substituable, testable en isolation.