10 - Architectures event-driven: pub-sub, streaming, sourcing

L’event-driven n’est pas un monolithe architectural, mais un ensemble de styles qui se combinent selon les objectifs. Une banque moderne doit concilier rapidité d’exécution, traçabilité, résilience, audit, et capacité de reconstruction. Le choix du style d’architecture détermine la manière dont ces exigences sont satisfaites, et les compromis acceptés.

Cet article propose une typologie claire et une matrice de choix. Les exemples sont bancaires (virements, comptes, fraude, KYC, reporting), car ce domaine met en lumière les contraintes réelles: invariants métier, conformité et réversibilité.

Typologie des architectures event-driven

1) Pub/Sub: diffusion simple et découplée

Le pub/sub est la forme la plus simple. Un producteur publie un event, des consommateurs s’abonnent. Le producteur ne connaît pas ses consommateurs, et peut évoluer sans coordination directe.

Exemple bancaire: AccountOpened est publié par Core Banking et consommé par le CRM, le backoffice, et le service Notifications. Chaque consumer traite l’event selon ses propres règles, sans couplage direct avec l’émetteur.

Forces: simplicité, fan-out naturel, couplage faible. Limites: ne suffit pas si l’on doit reconstruire l’état ou rejouer des flux.

2) Event streaming: le log comme colonne vertébrale

Le streaming ajoute la persistance et le replay. Chaque event est écrit dans un log durable, partitionné et ordonné par clé. Les consumers peuvent relire l’historique pour reconstruire des vues ou recalculer des métriques.

Exemple bancaire: les transactions d’un compte sont streamées et rejouées pour recalculer un scoring fraude en cas de modèle mis à jour. Le log devient une source opérationnelle, pas seulement un bus de notification.

Forces: relecture, analytics temps réel, pipelines de transformation. Limites: gouvernance stricte (schémas, retention, clé, volume).

3) Event sourcing: l’état reconstruit par l’historique

L’event sourcing élève le log d’events au rang de source de vérité. Le state est reconstruit par replay; les vues sont des projections. Dans une banque, un ledger peut être event-sourcé: chaque écriture comptable est un événement, et les soldes sont dérivés.

Cette approche impose des règles spécifiques: - Replay déterministe: les effets externes (notifications, appels AML, FX) doivent être isolés derrière une gateway contrôlable. - Journalisation des appels externes: si un taux FX influe sur l’état, il doit être loggé pour ne pas fausser un replay. - Snapshots: pour éviter de rejouer des millions d’events à chaque rebuild. - Corrections: on ajoute TransferReversed au lieu de modifier l’historique.

Event sourcing apporte une puissance d’audit et de reconstruction unique, mais le coût cognitif et opérationnel est élevé.

4) CQRS: séparer écriture et lecture

Le Command Query Responsibility Segregation (CQRS) sépare le write model (commands) du read model (queries). Le write model protège les invariants, le read model optimise la lecture. Cette séparation est très utile quand les besoins de lecture et d’écriture divergent fortement.

Exemple bancaire: le write model gère les règles de solde et la conformité, le read model expose une vue agrégée pour l’app mobile. Les projections peuvent être alimentées par events.

Forces: simplification du write model, performance en lecture, scalabilité. Limites: complexité opérationnelle, duplication des données, gestion de l’eventual consistency.

CQRS: cohérence et expérience utilisateur

CQRS impose un choix explicite sur la cohérence. Une projection peut être en retard, ce qui crée un décalage perçu par l’utilisateur. Dans une banque, on mitige cela en affichant des statuts (“en cours”, “settled”) et en implémentant un read-your-writes via: - token de cohérence renvoyé par le write model, - fallback du read model vers le write model, - cache session pour afficher la décision immédiate.

Matrice de choix rapide

Style Quand l’utiliser Coûts/risques Exemple bancaire
Pub/Sub Notifications, diffusion simple Workflow implicite, faible traçabilité AccountOpened -> CRM, Notifications
Streaming Relecture, temps réel, analytics Gouvernance forte, choix de clé TransferInitiated -> scoring fraude
Event sourcing Audit complet, reconstruction Complexité élevée, replay Ledger comptable
CQRS Lectures massives, write model complexe Projections, cohérence éventuelle Solde app mobile

Cette matrice n’est pas exclusive: on peut combiner plusieurs styles dans un même système, à condition de documenter les frontières et les contrats.

Topologies et circulation des events

Les styles architecturaux ne suffisent pas: la topologie du flux détermine la propagation réelle des événements.

  • Fan-out: un event alimente plusieurs consumers (fraude, ledger, notifications). C’est la topologie de base des events d’intégration.
  • Pipeline: un consumer enrichit l’event et publie un nouvel event. Exemple: TransferInitiated -> TransferRiskScored -> TransferApproved.
  • Hub-and-spoke: un broker central relie les domaines. Utile dans un écosystème bancaire multi-entités, mais impose une gouvernance stricte.
  • Mesh contrôlé: plusieurs flux interconnectés avec des contrats explicites. Sans gouvernance, cela dégénère en “spaghetti” d’événements.

Le backpressure doit être traité: un service lent ne doit pas bloquer le reste. Kafka isole les consommateurs, mais le design des groupes et le niveau parallélisme sont critiques.

Contrats d’événements et modèles d’échanges

Public vs private events

Un event public est un contrat d’intégration entre domaines. Il doit être stable, versionné, et documenté. Un event private est interne à une équipe.

En banque, TransferSettled ou AccountFrozen sont publics, alors qu’un event interne de recalcul de cache de scoring est private.

Fact vs delta

  • Fact: décrit un fait complet (ex: TransferSettled avec montant, devise, comptes). Idéal pour les contracts publics.
  • Delta: décrit un changement partiel (ex: balance_delta). À réserver aux flux private, car il dépend du contexte.

Notification vs state transfer

L’event notification est léger et implique un appel retour. L’event-carried state transfer transporte l’état nécessaire pour éviter un couplage runtime. Dans une banque, on privilégie souvent le state transfer pour résilience et réduction de latence, surtout sur les flux critiques.

Kafka comme backbone: mécaniques essentielles

Kafka combine pub/sub et streaming avec des primitives simples: - Topics: catégories d’events. - Partitions: ordre garanti par clé, parallélisme. - Consumer groups: un event par partition et par groupe. - Offsets: position de lecture, base des replays. - Retention/compaction: conservation temporelle ou par clé.

La clé de partition est un choix métier. Pour des opérations sur compte, on utilise account_id afin de préserver l’ordre. Pour des flux de virement, on peut utiliser transfer_id si l’ordre par transfert est suffisant. Un mauvais choix casse l’ordre et compromet les invariants.

Naming des topics

Une nomenclature stable est indispensable: <environment>.<service>.<domain>.<type>.<action>.<version>.

Exemples: - prod.corebanking.account.evt.opened.v1 - prod.payments.transfer.cmd.initiate.v1 - prod.risk.transfer.evt.scored.v1 - prod.corebanking.transfer.dlq.v1

Le type (cmd, evt, dlq) clarifie l’intention et évite les mélanges.

Gouvernance et performance

Schémas et compatibilité

Un event public doit être versionné. Les changements additifs sont préférés, les suppressions nécessitent une version majeure. Une registry (Avro, Protobuf, JSON Schema) garantit la compatibilité et réduit les incidents.

Retention vs compaction

  • Retention: conservation sur une durée fixe, utile pour l’audit.
  • Compaction: conserve le dernier event par clé, utile pour des référentiels comme les bénéficiaires ou les profils KYC.

Compression et coût

La compression réduit la bande passante et le stockage. snappy ou lz4 conviennent aux flux temps réel, zstd aux flux volumineux à latence tolérable. La banque doit arbitrer entre latence et coût d’infrastructure.

Lag et capacité de rattrapage

Un consumer peut accumuler du lag. L’architecture doit prévoir des mécanismes de rattrapage: scaling du consumer group, batch size adapté, replay contrôlé. Sur des flux financiers, un backlog massif devient un risque opérationnel et réglementaire.

L’observabilité fait partie de l’architecture: métriques de lag, tracing distribué, et corrélation correlation_id/causation_id sont indispensables. Sans ces signaux, diagnostiquer un incident de paiement devient coûteux et risqué pour la conformité.

Sécurité et conformité: contraintes non négociables

Dans un contexte bancaire, l’architecture event-driven doit intégrer la sécurité dès la conception. Les events peuvent contenir des données sensibles (PII, informations de compte, transactions). Les mesures typiques incluent: - Chiffrement en transit (TLS) et au repos (disques, topics sensibles). - ACLs sur les topics pour limiter l’accès aux seuls domaines autorisés. - Masquage/Tokenisation des données sensibles dans les events publics. - Traçabilité des accès pour répondre aux audits et investigations.

La conformité impose aussi des choix de rétention explicites. Certains events doivent être conservés longtemps (audit financier), d’autres doivent être purgeables (données personnelles). Cela influence la politique de retention, la compaction, et parfois la séparation physique des topics par niveau de sensibilité.

Découpage par domaines et ownership

Une architecture event-driven robuste s’appuie sur un découpage par domaines (core banking, paiements, fraude, CRM). Chaque domaine possède ses events et définit ses contrats publics. Cela évite les ambiguïtés et clarifie qui est responsable des évolutions de schéma. Dans les banques, il est courant d’avoir un catalogue d’events officiel: propriétaire, description métier, SLA, rétention, compatibilité. Ce catalogue réduit les erreurs d’intégration et sert de source de vérité pour les équipes.

Migration progressive: cohabiter avec le synchrone

Peu de banques peuvent basculer vers un modèle entièrement event-driven du jour au lendemain. La stratégie la plus réaliste est la migration progressive: on introduit des events sur un domaine limité, puis on étend. Le pattern “strangler” est courant: un service legacy continue de répondre aux requests synchrone, mais publie en parallèle des events qui alimentent de nouveaux consumers. Petit à petit, ces consumers remplacent des traitements anciens.

La cohabitation exige des frontières claires. Un domaine reste synchrone pour les décisions critiques (acceptation d’un virement), tandis que l’asynchrone sert la diffusion, l’audit et la projection. Cette dualité est normale et doit être assumée comme un choix d’architecture, pas comme une dette technique.

Cas bancaire fil rouge: virement international

1) Le client initie un virement international (InitiateTransfer). 2) Le service Virements valide les règles (solde, KYC, limites) et publie TransferInitiated. 3) Le service FX consomme l’event, fixe le taux, et publie TransferFxFixed. 4) Le service Fraude calcule un score (TransferRiskScored). 5) Le service Ledger écrit les écritures double-entry et publie TransferSettled. 6) Le service Notifications informe le client. 7) Le service Reporting consomme l’ensemble des events et calcule le volume journalier, avec le temps d’événement comme référence réglementaire.

On observe ici une combinaison de styles: pub/sub pour la diffusion, streaming pour les transformations (FX, fraude), event sourcing pour le ledger, CQRS pour les vues client. Chaque style répond à un besoin précis, sans imposer une architecture globale inutilement complexe.

Pièges à éviter

  • Mélanger commands et events dans un même topic.
  • Utiliser un topic global pour toute la banque (gouvernance impossible).
  • Changer un schéma public sans versioning explicite.
  • Penser que “tout doit être asynchrone”: la décision critique reste synchrone.
  • Imposer CQRS ou event sourcing sans justification métier claire.

Conclusion

Choisir une architecture event-driven, c’est choisir où vit l’historique, comment circule l’information, et quels contrats lient les domaines. Dans un système bancaire, ces choix sont des décisions de risque autant que des choix techniques. Une fois la typologie clarifiée, la suite logique est de formaliser le vocabulaire et les invariants qui rendent ce modèle robuste.