15 - Patterns d'intégration: Outbox, CDC et déduplication
Publier un event et écrire en base dans la même transaction est un problème classique. Sans pattern, on obtient des incohérences: l’event part mais la transaction échoue, ou l’inverse. Dans un contexte bancaire, ce scénario est catastrophique: il produit des écarts comptables et des audits impossibles.
Cet article détaille les patterns d’intégration clés: Transactional Outbox, CDC (Change Data Capture), déduplication et stratégies d’acheminement.
Le problème du dual write
Deux systèmes sont mis à jour sans atomicité. En cas de panne entre les deux,
l’état devient incohérent. Exemple bancaire: le virement est enregistré en base,
mais l’event TransferInitiated n’est jamais publié. Les services fraude ou
notification ne réagissent pas, alors que le virement existe comptablement.
À l’inverse, si l’event est publié mais que la transaction échoue, les consumers agissent sur un fait qui n’existe pas. Ce type d’incohérence est très coûteux à corriger en banque.
Transactional Outbox
Le pattern Outbox consiste à écrire l’event dans une table outbox dans la
même transaction que les données métier. Un relais se charge ensuite de
publier les events vers Kafka.
Étapes classiques
1) Transaction métier dans la base (ex: créer un virement).
2) Insertion d’une ligne dans outbox.
3) Un relay lit l’outbox et publie dans Kafka.
4) L’event est marqué comme traité.
Avantages
- Atomicité: état et event restent cohérents.
- Simplicité conceptuelle.
- Contrôle sur le format publié.
Inconvénients
- Nécessite un relay fiable.
- Risque de backlog si le relay est en panne.
Modéliser la table outbox
Une outbox n’est pas qu’un simple log. Sa structure détermine la capacité à
publier, rejouer et tracer les events. Un schéma typique contient:
- id (clé primaire technique),
- aggregate_type et aggregate_id (pour le partitioning),
- event_type et schema_version,
- payload (JSON/Avro),
- occurred_at, correlation_id, causation_id,
- status (pending, published, failed),
- retry_count et last_error.
Dans une banque, il est crucial d’inclure des champs de traçabilité: un flux complet de virement doit pouvoir être reconstitué sans ambiguïté. La table outbox devient ainsi une source d’audit complémentaire au log Kafka.
Partitioning et ordering
Le relay doit publier avec une clé de partition alignée sur l’agrégat métier.
Si la table outbox stocke account_id, l’event doit être publié avec cette clé
pour préserver l’ordre des opérations sur le compte. Publier avec une clé
aléatoire casse la cohérence et rend les replays dangereux.
CDC (Change Data Capture)
Le CDC capture les changements dans la base et les publie dans Kafka. Des outils comme Debezium lisent le log transactionnel et transforment les changements en événements.
Avantages
- Pas besoin de relay applicatif.
- Capture exhaustive des changements.
- Faible impact sur l’application métier.
Inconvénients
- Moins de contrôle sur le format des events.
- Peut exposer des détails internes.
- Nécessite une gouvernance stricte pour éviter les fuites de PII.
CDC + Outbox
Une approche courante en banque est de combiner CDC et outbox: l’application écrit une table outbox, et Debezium publie cette table. On conserve la maîtrise fonctionnelle tout en évitant un relay custom.
CDC en profondeur: snapshots et transactions
Le CDC ne se limite pas au streaming continu. Les connecteurs réalisent d’abord un snapshot initial, puis suivent le log de transactions. En banque, ce snapshot doit être maîtrisé: il peut générer un volume important et doit être coordonné avec les périodes de charge.
Les transactions sont un point délicat. Un connecteur CDC doit préserver l’ordre et respecter les frontières transactionnelles. Sans cela, un consumer pourrait recevoir un event partiel qui contredit l’état réel. Une bonne pratique est de publier des events uniquement lorsque la transaction est validée, et de filtrer les opérations intermédiaires.
Déduplication et idempotence
Même avec Outbox, les doublons sont possibles (retries, crash du relay). La solution est l’idempotence côté consumers.
Stratégies:
- Table de déduplication par event_id.
- Upsert sur projection par clé métier.
- Transactions Kafka (consume-transform-produce).
Dans un ledger bancaire, la déduplication est obligatoire pour éviter des écritures doubles.
Exactly-once dans les pipelines d’intégration
Lorsque le relay consomme l’outbox et publie dans Kafka, il est possible
Pour des flux critiques (ledger, paiements instantanés), on utilise les transactions Kafka. Le relay lit une batch d’outbox, publie les events dans une transaction, puis marque les lignes comme publiées. Si la transaction échoue, les lignes restent en état pending et seront retentées. Ce mécanisme réduit les risques d’event publié sans mise à jour cohérente de l’outbox.
Cette approche impose une discipline stricte sur la gestion des erreurs et la
configuration des consumers downstream (isolation.level=read_committed).
Stratégies de publication
Polling
Un worker lit périodiquement l’outbox et publie. Simple à implémenter, mais moins performant. Acceptable pour des flux non temps réel.
Streaming CDC
Debezium ou un autre connecteur lit le journal de transaction et publie en streaming. Plus performant, mais plus complexe à opérer.
Hybrid
Un polling court terme (latence faible) et un CDC pour rattrapage peuvent être combinés dans les architectures critiques.
Consistency vs latency
L’outbox garantit la cohérence, mais peut introduire une latence. En banque, la cohérence prime sur la latence absolue. Un délai de quelques centaines de millisecondes est acceptable si l’audit est garanti.
Conception du payload et stabilité
L’outbox ne doit pas devenir un dumping de modèle interne. Le payload doit
rester aligné avec le contrat public: un fait clair, immuable et versionné.
Dans un flux bancaire, cela implique de préférer un event TransferInitiated
avec un identifiant stable et les montants en unités mineures, plutôt qu’un
snapshot complet d’un objet transactionnel interne. Cette discipline limite le
couplage et réduit la surface de migration.
Pour les environnements multi-tenant, il est conseillé d’inclure un champ
tenant_id ou organization_id dans l’envelope pour faciliter le routing et
le contrôle d’accès. Cela évite des topics séparés par tenant et simplifie la
gouvernance globale.
Bonnes pratiques de base
- Utiliser un topic dédié pour l’outbox.
prod.payments.transfer.outbox.v1
- Publier dans des topics d’events stables et versionnés.
prod.payments.transfer.evt.initiated.v1
- Prévoir un topic de DLQ pour le relay.
prod.payments.transfer.dlq.v1
- Documenter le format des events publics.
Exemple bancaire: virement avec Outbox
1) API reçoit InitiateTransfer.
2) Transaction en base: création du virement.
3) Insertion de TransferInitiated dans la table outbox.
4) Debezium publie dans Kafka.
5) Consumers fraude, ledger, notification réagissent.
En cas de crash entre 2 et 4, l’event reste en base et sera publié au redémarrage.
Déduplication avancée et replay
Lors d’un replay, un consumer peut recevoir des milliers d’events déjà traités. La table de déduplication doit être dimensionnée pour conserver un historique suffisant. En banque, on garde souvent un horizon long (mois ou années) pour les flux comptables.
Outbox et modèles transactionnels
Dans un contexte multi-bases, il est impossible d’avoir une transaction globale. L’outbox est la solution pragmatique: chaque domaine écrit son état et publie ses facts. La cohérence globale est assurée par des sagas ou par la reconstruction via replay.
CDC et sécurité
Le CDC peut exposer des champs sensibles (PII). Il est impératif de: - filtrer les colonnes sensibles, - masquer les données, - appliquer des ACLs strictes.
Dans une banque, on préfère capturer uniquement les tables outbox plutôt que les tables métier complètes.
Orchestration des erreurs
Un relay qui échoue doit être capable de: - retenter avec backoff, - isoler les événements non publiables, - journaliser le contexte d’échec.
Sans cette orchestration, les erreurs deviennent invisibles et l’outbox se remplit silencieusement.
Tests de résilience
Un pattern d’intégration doit être éprouvé. Les banques utilisent souvent des tests de chaos ciblés: arrêt du relay, coupure réseau, saturation du broker. L’objectif est de vérifier que les events restent cohérents et que la reprise ne génère pas de doublons. Ces exercices renforcent la confiance dans l’outbox et mettent en évidence les faiblesses opérationnelles avant un incident réel.
Observabilité et exploitation
Un pattern d’intégration ne vaut que s’il est observable. Les métriques clés
pour l’outbox incluent:
- taille de la table outbox,
- latence entre occurred_at et publication Kafka,
- taux d’échec et nombre de retries.
En banque, ces métriques doivent être corrélées aux SLA métiers. Un backlog non maîtrisé peut entraîner des retards de fraude ou de reporting. Des alertes sur la croissance de l’outbox sont indispensables pour déclencher un mode de rattrapage.
Stratégies de nettoyage
Une outbox grandit rapidement. Il faut définir une politique de purge: - suppression après publication et délai de sécurité, - archivage des events critiques pour audit, - partitionnement de la table par date pour faciliter la maintenance.
Sans nettoyage, l’outbox devient un goulot d’étranglement. En banque, on conserve souvent un historique minimal pour audit interne, mais on déplace les volumes longs vers un stockage dédié.
Architecture du relay
Le relay est un composant clé. Il doit être pensé comme un service de production, pas comme un simple script. Les éléments essentiels: - parallélisme contrôlé (workers alignés sur les partitions Kafka), - idempotence des publications, - journalisation des erreurs et métriques d’activité.
Dans une banque, le relay doit aussi respecter les contraintes de sécurité: authentification Kafka, isolation réseau, et accès restreint aux tables outbox. Il est fréquent de déployer le relay dans la même zone que la base de production pour réduire la latence et la surface d’erreur.
Migration progressive vers l’outbox
Beaucoup d’organisations possèdent déjà des intégrations synchrones. Une migration brute est risquée. On adopte alors un pattern progressif: - le service legacy continue à répondre en synchrone, - l’outbox publie en parallèle des events, - les nouveaux consumers se greffent progressivement.
Cette migration permet de valider les projections et la qualité des events avant de désactiver les intégrations synchrones. En banque, c’est souvent la seule voie acceptable pour éviter les interruptions de service.
Comparatif Outbox vs CDC
| Critère | Outbox | CDC |
|---|---|---|
| Contrôle du format | Élevé | Moyen |
| Latence | Moyenne | Faible |
| Complexité | Moyenne | Élevée |
| Risque PII | Faible (si outbox dédiée) | Élevé |
| Audit | Très bon | Bon |
Dans un contexte bancaire, l’outbox dédiée avec CDC sur la table outbox est souvent le meilleur compromis.
Conclusion
Les patterns d’intégration sont la garantie de cohérence entre l’état métier et les events. Dans une banque, ils évitent les écarts comptables et facilitent l’audit. Transactional Outbox et CDC ne sont pas exclusifs: ils se complètent. Le choix dépend de la maturité technique, des exigences de latence et des contraintes réglementaires. Un design bien gouverné transforme ces patterns en fondations fiables pour les flux bancaires les plus sensibles. Il sécurise la confiance entre domaines et simplifie les audits réglementaires complexes durant la vie du système.