GetOut Sport
Backend microservices distribué pour une startup de réservation sportive — paiements Stripe orchestrés par Temporal, services polyglottes (Java/Micronaut + Go) et une plateforme Kubernetes GitOps complète.
Rôle : Lead Developer
Vue d'ensemble
GetOut Sport est une startup qui réunit les installations sportives sur une seule plateforme où l'on réserve et joue ensemble. En tant que Lead Developer, j'ai conçu l'architecture backend et piloté le développement — le cœur de mon travail était le backend distribué et orienté paiement, et la plateforme cloud-native qui le fait tourner, avec des clients iOS, Android et web par-dessus.
Le système est un ensemble de microservices déployés indépendamment, des flux d'argent orchestrés par Temporal, et une plateforme Kubernetes gérée entièrement en GitOps.
Problème & contexte
Un vrai produit avec du vrai argent : les organisations vendent des réservations, des cours et des abonnements récurrents dans plusieurs pays (France, Espagne), donc le backend doit gérer paiements, taxes et facturation au cordeau — et rester correct sous les retries, les requêtes concurrentes et les pannes partielles. Il fallait aussi sortir progressivement d'un monolithe Laravel existant sans interrompre le produit.
Architecture & décisions techniques
- Microservices polyglottes, une base par service. Les domaines à état (events, paiements) tournent en Java 21 / Micronaut avec leur propre schéma PostgreSQL ; l'orchestration asynchrone et les notifications tournent en Go. Pas de base partagée — les services s'intègrent via des APIs et des événements.
- Temporal comme colonne vertébrale d'orchestration. Les flux longs qui touchent à l'argent sont des workflows Temporal plutôt que des jobs ad hoc : un
QuoteLifecycleWorkflow(capture → ledger → factures), unEventDispatchWorkflow, et des workers de création / cycle de vie d'événements. La facturation récurrente repose sur les Temporal Schedules — chaque plan est un schedule cron qui déclenche un workflow de facturation court et prélève off-session, au lieu de s'appuyer sur les Stripe Subscriptions. - Justesse des paiements par conception. Tout paiement passe par un point d'entrée unique, le Quote : Stripe Tax sur les lignes brutes, remises appliquées ensuite, puis un PaymentIntent Stripe (Connect) avec 3DS. L'idempotence est en couches — clés d'idempotence Stripe (
billing-run-<id>), le run ID du workflow Temporal comme clé de dédup, unIdempotency-KeyHTTP avec index partiel unique, et un verrouillage pessimiste (SELECT … FOR UPDATE) sur les lignes de plan pour éliminer les races last-write-wins entre un billing run qui se règle et un utilisateur qui annule. - Un service de paiement agnostique au domaine. Quotes, paiements et factures s'appuient sur une paire générique
(referenceType, referenceId)— réservations d'événements, plans récurrents et futurs produits réutilisent ainsi le même pipeline. - Identité centralisée. L'authentification est déléguée à Ory Kratos (identité/sessions) + Ory Hydra (OAuth2/OIDC) plutôt que réimplémentée dans chaque service.
- Communication entre services. REST avec des clients OpenAPI générés entre services, gRPC vers le notifier Go (email via Resend, push via FCM), et des task queues Temporal pour l'asynchrone.
- Plateforme API-first. Les specs OpenAPI des services alimentent des GitHub Actions réutilisables qui publient des SDK typés TypeScript (Orval) et Dart, consommés par les clients web et mobile — les clients n'écrivent jamais d'appels API à la main.
- Migration strangler. De nouveaux services Micronaut/Go reprennent progressivement le rôle du backend Laravel historique (domaine catalogue/organisation), qui continue de servir jusqu'à ce que chaque capacité soit remplacée.
Plateforme cloud-native
Toute la plateforme est gérée par Terraform et réconciliée par Git :
- Kubernetes sur infrastructure managée — PostgreSQL via Google Cloud SQL (sidecar proxy), ingress NGINX, DNS Cloudflare.
- GitOps avec ArgoCD — services et infra réconciliés depuis Git ; Sealed Secrets rend les secrets sûrs à commiter.
- TLS via cert-manager + Let's Encrypt ; observabilité avec Prometheus, Grafana, Loki + Promtail pour les logs, et du tracing OpenTelemetry sur HTTP et JDBC.
- CI/CD sur GitHub Actions — versioning sémantique piloté par les Conventional Commits, builds Docker multi-stage avec support des images natives GraalVM, tests d'intégration via Testcontainers.
Difficultés & apprentissages
- Faire les flux d'argent correctement. Le difficile dans les paiements n'est pas le happy path — ce sont les retries, les double-soumissions et les races. Empiler clés d'idempotence, dédup sur les run IDs Temporal et verrouillage pessimiste, c'est ce qui fait que « prélever une fois, exactement une fois » tient réellement.
- Transactions vs. appels externes. Garder une connexion DB ouverte pendant des appels Stripe et Temporal gRPC épuise le pool sous charge — la solution est un pattern outbox qui sort les appels externes du périmètre
@Transactional. L'identifier tôt a façonné la conception du service. - Orchestration plutôt que chorégraphie. Modéliser les abonnements en Temporal Schedules (pas en Stripe Subscriptions) a donné un contrôle total sur les billing runs, les fenêtres de retry (7 jours en off-session) et la réconciliation — au prix de plus de logique à porter.
- La réalité du multi-pays. Comptes Stripe par pays, calcul de taxes, devises et fuseaux horaires (
Europe/Paris) doivent être first-class, pas des après-coups.
Stack
- Backend — Java 21 / Micronaut (events, paiements), Go (workers Temporal, notifier), Flyway, gRPC
- Orchestration — Temporal (workflows + Schedules), cycle de vie de paiement en saga
- Paiements — Stripe Connect, Stripe Tax, 3DS, ledger + facturation
- Données — PostgreSQL (un schéma par service) sur Google Cloud SQL, Redis
- Identité — Ory Kratos + Ory Hydra (OAuth2/OIDC)
- Plateforme — Kubernetes, Terraform, ArgoCD, Sealed Secrets, cert-manager, ingress NGINX, Cloudflare
- Observabilité — Prometheus, Grafana, Loki, Promtail, OpenTelemetry
- CI/CD — GitHub Actions, génération de SDK pilotée par OpenAPI (Orval / Dart), images natives GraalVM