21 - Design end-to-end d'un système event-driven

Le design end-to-end d’un système event-driven n’est pas un exercice abstrait: c’est une discipline qui relie le métier, la donnée, l’infrastructure et la résilience opérationnelle. En contexte bancaire, chaque event est un acte traçable qui peut être audité des années plus tard. La qualité du design se mesure à la capacité de comprendre, rejouer et prouver un flux, tout en gardant une latence acceptable et une sécurité stricte.

Ce guide propose une démarche pragmatique, de la cartographie métier jusqu’à l’exploitation. L’objectif n’est pas d’être exhaustif, mais de fournir un cadre réutilisable pour construire des pipelines robustes, versionnés et compréhensibles.

Principes directeurs

Un bon design end-to-end repose sur quelques invariants: - Contract-first: un event est un contrat durable, pas un message jetable. - Idempotence: tout consumer doit tolérer les doublons sans effet métier. - Asynchronisme explicite: le client comprend ce qui est engagé et ce qui est en propagation. - Observabilité intégrée: chaque event est traçable, corrélé et mesurable. - Sécurité par défaut: chiffrement, ACLs, classification des données.

Ces principes sont particulièrement critiques en banque, où les obligations réglementaires imposent une traçabilité fine et une capacité de reconstitution.

Étape 1: cartographier le domaine

La conception débute par une cartographie métier. L’event storming aide à identifier les événements clés et les invariants. L’enjeu est de découper le système en domaines cohérents: paiements, ledger, fraude, KYC, onboarding.

Dans une banque, l’agrégat Compte possède des invariants stricts: solde jamais négatif, plafond journalier, autorisations multi-signatures. Ces règles ne doivent pas être disséminées. Le design doit rendre explicites les frontières entre ce qui décide (command) et ce qui constate (event).

Étape 2: choisir le pattern d’event

Tous les events ne se ressemblent pas. Il faut choisir le bon pattern selon le besoin: - Event Notification: payload minimal, le consumer récupère le détail (ex: BeneficiaryAdded déclenche un enrichissement KYC). - Event-Carried State Transfer: payload complet pour éviter les appels synchrones (ex: diffusion des détails d’un compte client vers un CRM). - Event Sourcing: l’event log devient la source de vérité (ex: ledger bancaire, audit des écritures comptables).

Un piège classique est l’event utilisé comme commande passive-agressive: le producteur attend qu’un consumer réalise une action critique, mais le message est formulé comme un event. Si l’intention est prescriptive, utilisez une commande explicite. Sinon, vous perdez la clarté contractuelle.

Étape 3: définir le contrat d’event

Un event exploitable en production contient un envelope technique et un payload métier. L’envelope fournit les champs nécessaires au tracing et au versioning, tandis que le payload capture le fait métier.

Exemple (virement initié):

{
  "event_id": "0f58b8b1-3e1e-4e1a-9d2f-9ad8c3b0f662",
  "type": "TransferInitiated",
  "occurred_at": "2024-01-22T08:00:00Z",
  "schema_version": 1,
  "correlation_id": "corr-6b2a",
  "causation_id": "cmd-91f3",
  "tenant": "retail-eu",
  "payload": {
    "transfer_id": "TRF-913",
    "from_account": "ACC-001",
    "to_account": "ACC-987",
    "amount_minor": 59090,
    "currency": "EUR",
    "channel": "mobile"
  }
}

En banque, il est essentiel de distinguer occurred_at (temps métier) de processed_at (temps technique) pour la réconciliation. Le schéma doit être versionné (Avro/Protobuf + Schema Registry) avec une stratégie de compatibilité claire (backward ou full selon la criticité).

Étape 4: design des topics

La topologie des topics structure la lisibilité du système. Commencez par séparer public vs private, puis fact vs delta. Un event public expose un contrat stable, tandis qu’un event private peut évoluer plus vite.

Modèles de nommage recommandés:

<environment>.<service>.<domain>.<action>.<version>
<environment>.<tenant>.<service>.<domain>.<action>.<version>
<environment>.<service>.<domain>.<type>.<action>.<version>

Bonnes pratiques de base

  • prod.payments.transfer.evt.initiated.v1
  • prod.payments.transfer.cmd.initiate.v1
  • prod.payments.transfer.dlq.v1
  • prod.corebanking.account.evt.opened.v1
  • prod.mobilebanking.beneficiary.evt.confirmed.v1

Ces conventions clarifient la nature du message (cmd/evt), le domaine et la version. Ajoutez systématiquement un topic de Dead Letter pour isoler les messages non traitables et faciliter le reprocessing.

Étape 4 bis: gouvernance des schémas et catalogue d’events

Un système event-driven mature traite ses schémas comme des API. Chaque schéma doit avoir un owner, un cycle de vie, des règles de compatibilité et une politique de dépréciation explicite. En banque, changer le sens d’un champ peut avoir des impacts réglementaires; on préfère donc des évolutions additives, versionnées, avec des tests de compatibilité automatisés en CI.

Le Schema Registry devient un garde-fou: imposez un mode BACKWARD ou FULL selon la criticité du domaine. Si un consumer ne peut pas évoluer vite, le producteur doit rester compatible. C’est particulièrement vrai pour les topics publics qui alimentent le reporting réglementaire et la lutte contre la fraude.

Mettez en place un event catalog: pour chaque topic, documentez le domaine, le propriétaire, le SLA, la rétention, les clés de partition, les champs sensibles, les consommateurs connus et les contraintes de traitement. Ce catalogue sert d’interface entre équipes et évite les couplages implicites. Un consumer doit savoir si un event est public (contrat stable) ou private (évolue plus vite). Sans cette discipline, la dette de compatibilité devient ingérable à l’échelle.

Étape 5: partitioning et clé métier

La clé de partition gouverne l’ordre des events. En banque, la cohérence d’un compte est souvent prioritaire: choisissez account_id comme clé pour toutes les écritures liées au solde. Pour un virement international, transfer_id peut être plus pertinent pour préserver l’ordre des étapes.

Exemple de clé structurée:

account.ACC-001

Si l’ordre est non pertinent et que l’objectif principal est la distribution, vous pouvez publier avec une clé null. Attention: vous perdez alors la garantie d’ordre, ce qui est rarement acceptable sur des flux comptables.

Le dimensionnement des partitions est un compromis entre parallélisme et coût opérationnel. En banque, on cherche à éviter les rebalances coûteux, donc on prévoit une marge de croissance (souvent 2 à 3x le débit actuel). Trop de partitions augmentent la latence des rebalances et la charge sur les brokers. Surveillez les hotspots (ex: un compte très actif) et prévoyez un sharding contrôlé si certains clients concentrent l’activité.

Étape 6: stratégie d’intégration

Les flux critiques nécessitent une cohérence forte entre l’écriture en base et la publication Kafka. Le Transactional Outbox reste la meilleure option: la transaction métier écrit l’état + l’event dans la même base, puis un relai publie vers Kafka. Pour des systèmes existants, la CDC (Change Data Capture) permet d’extraire les changements sans modifier l’application.

L’idempotent producer et les transactions Kafka permettent de livrer des pipelines critiques (ex: ledger). Mais ne confondez pas les garanties: Kafka fournit un exactly-once au niveau du broker, pas au niveau métier. C’est au consumer de gérer l’idempotence et la compensation.

Étape 7: concevoir les consumers

Un consumer robuste suit quelques règles: - Commit d’offset après l’effet métier. - Déduplication via event_id stocké en base. - Gestion des retries (backoff, DLQ) sans blocage du flux. - Limitation du parallelisme si l’ordre est critique.

Dans une banque, un consumer de ledger doit traiter un event de crédit exactement une fois du point de vue métier. Cela implique un modèle idempotent, par exemple un index unique sur (event_id, account_id) qui empêche les doublons.

Étape 7 bis: gestion des erreurs et reprocessing

Un pipeline bancaire doit distinguer les erreurs métier des erreurs techniques. Une règle métier (solde insuffisant, KYC incomplet) doit produire un event explicite (TransferRejected) afin que la décision soit auditable et consommable par d’autres systèmes. Une erreur technique (timeout, payload corrompu) doit être isolée dans un flux de retry puis, en dernier recours, dans un DLQ.

Les topics de retry par paliers (ex: retry-5m, retry-1h) permettent de rejouer les messages sans bloquer le flux principal. Chaque tentative doit conserver les headers d’origine (event_id, correlation_id, attempt) afin de diagnostiquer précisément l’incident. En banque, un reprocessing massif doit être contrôlé: il peut déclencher des notifications ou recalculs de risque à grande échelle. Documentez qui peut relancer un flux, avec quels garde-fous et quelles fenêtres de reprise.

Étape 8: orchestration vs chorégraphie (saga)

Un virement international mobilise plusieurs services: AML, FX, ledger, notifications. Deux stratégies existent: - Chorégraphie: chaque service réagit à un event et publie le suivant. - Orchestration: un orchestrateur central (saga) pilote la séquence.

La chorégraphie favorise le découplage mais peut masquer le flux global. L’orchestration apporte de la visibilité mais crée un point central. Pour des flux bancaires critiques, un orchestrateur peut faciliter la conformité et le support opérationnel, à condition d’être résilient.

Étape 9: observabilité et SLA

Sans observabilité, un système event-driven devient une boîte noire. Les métriques minimales à suivre: lag par consumer group, taux d’erreur, throughput, temps de traitement end-to-end. Ajoutez du tracing distribué avec correlation_id et causation_id pour reconstruire un flux complet.

Des dashboards orientés métier (ex: taux de virements validés vs rejetés) doivent être couplés aux métriques techniques. En banque, la visibilité métier prime sur les métriques purement système.

Étape 10: sécurité et conformité

Un event peut contenir des PII ou des données sensibles (IBAN, identité, KYC). Appliquez une classification des données et séparez les topics sensibles. Utilisez le chiffrement en transit (TLS) et au repos, avec rotation de clés.

Les ACL doivent être explicites par topic et par consumer group. Préférez le principe du least privilege et documentez les exceptions. Les audits exigent souvent une traçabilité des accès aux topics et aux schémas.

Étape 11: plan de migration

Migrer vers l’event-driven demande une stratégie progressive: 1) Capturer les changements (CDC ou outbox). 2) Construire des projections parallèles. 3) Exposer les nouveaux read models. 4) Décommissionner le legacy une fois la parité atteinte.

Dans une banque, la coexistence legacy / event-driven est fréquente. Le piège est la divergence silencieuse: mettez en place des contrôles de parité et des audits réguliers (reconciliations automatisées).

Étape 12: tests et validation

Les tests doivent couvrir le contrat, la compatibilité et le replay: - Contract tests producer/consumer (schema compatibility). - Replay tests sur des snapshots historiques. - Tests de charge pour valider la capacité de partitioning. - Chaos tests pour simuler pannes broker ou réseau.

Dans un contexte bancaire, les scénarios de rollback et de compensation doivent être validés comme des cas d’usage normaux, pas comme des exceptions.

Exemple bancaire complet: virement international

Voici un flux complet, du command jusqu’aux écritures comptables: 1) InitiateTransfer (command) valide le mandat et la conformité AML. 2) Publication TransferInitiated sur prod.payments.transfer.evt.initiated.v1. 3) Service FX réserve un taux: TransferFxLocked. 4) Service risque publie TransferRiskApproved ou TransferRiskRejected. 5) Ledger applique TransferBooked et publie LedgerEntryPosted. 6) Service notification publie NotificationSent. 7) En cas d’échec tardif, une compensation émet TransferReversed.

Chaque étape est corrélée par correlation_id. Les projections de reporting peuvent reconstruire un état global sans dépendre des services transactionnels.

Conclusion

Un design end-to-end exige de traiter les events comme des contrats durables. Dans une banque, cela signifie: contrôles forts, gouvernance stricte et capacité à expliquer une décision des années après. Les systèmes event-driven sont puissants lorsqu’ils sont pensés comme un produit opérationnel, pas comme une simple intégration technique.

Documentez les décisions d’architecture (ADR), les compromis et les dérogations. Cette mémoire technique accélère les audits, facilite l’onboarding et évite les retours en arrière coûteux quand le système évolue.