Aller au contenu

DB partagée entre services

Anti-pattern. Service A et service B partagent une même base de données / une même table. Chaque déploiement de l'un risque de casser l'autre.

Symptômes

  • Une migration Alembic dans A casse B.
  • Une optimisation d'index dans B ralentit A.
  • Un verrou de ligne dans A bloque B.
  • Un DROP TABLE accidentel dans A perd les données de B.

Pourquoi c'est tentant

  • Pas de réseau (performance locale).
  • Pas d'API à versionner.
  • Jointures SQL faciles.
  • Transactions ACID cross-service "gratuites".

Pourquoi c'est mauvais

1. Couplage de schéma à l'exécution

Si A change la colonne users.email en users.contact_email, B doit être déployé en même temps. Le moindre décalage casse les deux services. La migration devient une opération coordonnée multi-équipe.

2. Couplage de performance

Une transaction longue dans A bloque les écritures de B sur la même table. Vous découvrez ça en prod, sous charge.

3. Fuite de données

Le schéma SQL ne fait pas la différence entre champs publics et internes. Un dev de B fait SELECT * FROM users et voit les hachages bcrypt, les tokens API et les données personnelles.

4. Pas d'évolution indépendante

A veut migrer Postgres → CockroachDB. Impossible sans migrer B en même temps. Vous êtes mariés.

Le bon motif

Chaque service a sa propre DB. Communication par :

  • Push HTTP signé entre services.
  • Read-model dénormalisé dans un service dédié.
  • Outbox transactionnel + worker de drain.
graph TB
    subgraph "❌ Avant"
        A1[Service A] -->|lit/écrit| DB1[(DB partagée)]
        B1[Service B] -->|lit/écrit| DB1
    end

    subgraph "✅ Après"
        A2[Service A] -->|lit/écrit| DB_A[(DB de A)]
        B2[Service B] -->|lit/écrit| DB_B[(DB de B)]
        A2 -->|POST /events<br/>JWT signé| B2
    end

"Mais on a un cas légitime !"

Trois exceptions où une DB partagée peut être justifiée :

  1. Motif transactional outbox côté émetteur : la table outbox_events du service A est lue par un worker du service A (pas par un autre service) — c'est juste de la file interne.
  2. Read-replica : service B lit une réplique read-only du service A pour de l'analytique. La lecture est read-only côté B. Mais c'est souvent mieux servi par un read-model dédié.
  3. Migration progressive : pendant qu'on découpe un monolithe en services, l'étape intermédiaire peut nécessiter une DB partagée. C'est transitoire, et chaque jour qui passe en DB partagée est un jour qui coûte.

Hors de ces cas, pas de DB partagée. La règle est simple parce que l'exception est dangereuse.

Référence en pratique

L'exception historique est api ↔ sync qui partagent un schéma SQLModel — documenté dans PATTERN_TRANSACTIONAL_OUTBOX.md. Tous les autres inter-services utilisent HTTP signé (mail-out, audit, search, extract).