Étude de cas · Labo résilience
Payment Platform Disaster Lab
Production / ingénierie de la résilience autour d’une plateforme de paiement distribuée (XOF, dual PSP, Kafka, ledger) — prouver la correctness financière quand le système n’est pas disponible à 100 %.
Ce n’est pas un PSP de production. C’est un lab pour démontrer qu’un système de paiement peut rester financièrement correct sous défaillances partielles — aucun paiement perdu, aucun traité deux fois.
Signaux
0
Double capture sur timeout
L’incertitude est first-class — un timeout ne déclenche jamais de failover aveugle.
D1–D10
Scénarios disaster
Hypothèses de défaillance documentées, du PSP down au canary de déploiement.
P0–P2
Livré aujourd’hui
Compose, payment SM, dual PSP + chaos, gateway JWT, control center SRE.
Règle d’or du routing
PSP A down (503 / CB OPEN) → failover PSP B. PSP A timeout / UNKNOWN → IN_RECONCILIATION — jamais PSP B. Confondre D1 et D2 est un défaut de design, pas un détail d’ops.
Problème
Construire une API de paiement « happy path » est facile. Le difficile commence quand le PSP timeout (a-t-il capturé ou non ?), le client retry après un timeout HTTP, un webhook arrive 3 fois, un consumer Kafka tombe pendant que les events s’accumulent, ou un déploiement casse 20 % du trafic. Haute disponibilité perçue et correctness financière divergent — un failover aveugle vers un second PSP peut créer un double débit ; un retry non idempotent aussi.
Solution
Une règle unique : aucun paiement perdu, aucun traité deux fois — même quand le système n’est pas disponible à 100 %. Chaîne d’invariants : Identity → Authorization → Idempotency → State Machine → PSP (incertitude first-class) → Ledger → Event → Reconciliation → Observability. Timeout PSP ≠ FAILED ; l’incertitude est IN_RECONCILIATION, pas une excuse pour appeler PSP B.
Contraintes
- Timeout PSP ≠ FAILED — l’incertitude est un état métier (IN_RECONCILIATION).
- Failover A→B seulement si échec certain (503 / CB OPEN / refus métier).
- Jamais d’event Kafka sans état paiement commit (transactional outbox).
- Database-per-service — isoler le schéma ledger append-only ; pas de jointure cross-DB.
- Simulateurs PSP avec chaos injectable à chaud — pas le vrai comportement réseau / SCA / settlement.
Flux d'architecture
- 01Gateway
- 02Payment SM
- 03Dual PSP
- 04Outbox / Kafka
- 05Réconciliation
Architecture
Spring Boot multi-services (domaines de défaillance isolables). API Gateway : JWT + /v1/payments + X-Correlation-Id. payment-service : create/GET, state machine, idempotence, transactional outbox. Dual psp-simulator A/B avec Resilience4j — failover sûr vs IN_RECONCILIATION. Compose : Postgres, Kafka, Schema Registry, Keycloak, Kafka UI. Le frontend est un control center SRE (Vite + React), pas un checkout marchand — observer, injecter, récupérer.
Décisions
- 01
Multi-services, pas monolithe
Pouvoir tuer un consumer ou un PSP isolément — de vrais failure domains pour le lab.
- 02
Database-per-service
Isoler le schéma ledger append-only ; pas de jointure cross-DB sur le chemin de démo.
- 03
Transactional outbox
Jamais d’event Kafka sans état paiement commit.
- 04
Timeout PSP = UNKNOWN
Pas de failover aveugle ; recon avant vérité — évite la double capture.
- 05
Failover seulement si échec certain
503 / CB OPEN / refus métier — pas timeout ni 5xx ambigu.
- 06
JSON Schema + Schema Registry
Contrats lisibles en démo ; évolution compatible CI sans cérémonie Avro pour l’instant.
- 07
Keycloak + API Gateway
Identity / JWT hors du chemin métier pour que le chaos reste centré sur le mouvement d’argent.
- 08
Simulateurs PSP + control center FE
Chaos à chaud via PUT /admin/chaos ; le frontend observe et récupère — pas un produit checkout.
Compromis
Correctness avant « API toujours 200 » implique plus d’IN_RECONCILIATION et une recon obligatoire. Services découpés + outbox achètent isolation et replay Kafka au prix d’une ops locale plus lourde. Un Postgres avec DB logiques simplifie le lab — pas le HA multi-instance de prod. JSON Schema privilégie la lisibilité démo face à l’évolution « battue » d’Avro. Les simulateurs donnent un chaos déterministe, pas le vrai réseau / SCA / settlement. Canary / K8s en phases tardives (P6) ; le FE en simulation peut diverger du BE réel tant qu’il n’est pas branché.
Stack
Ce que je ferais ensuite
- 01
P3 — Ledger + consumers Kafka : double-entry append-only, consumer idempotent, DLT, Schema Registry de bout en bout.
- 02
P4 — Notification + réconciliation : webhooks merchant ; MATCHED / MISSING / DUPLICATE / AMOUNT_MISMATCH ; poll statut PSP pour sortir de IN_RECONCILIATION.
- 03
P5 — Observability : traces OTel gateway → payment → PSP → Kafka → ledger ; dashboards KPI / SLO / CB / lag outbox.
- 04
P6 — Kubernetes + canary : Helm, probes, canary 5–20 %, rollback auto sur error rate.
- 05
P7 — Chaos documenté : remplir D1–D10 (hypothèse → observé → recovery) ; brancher le FE sur le vrai backend.
Vous cadrez un problème paiement ou résilience ?
Partagez les modes de défaillance qui comptent — je cartographierai invariants, risques et une première tranche réaliste.
