Données & reprise · Socle technique

Socle technique des schémas

Les tables présentes dans tout schéma de service. Elles ne portent aucune donnée métier : elles sont la condition pour qu'un service publie, consomme et rejoue sans produire de doublon. La forme de référence est celle du codeoutbox_events est déjà créée dans les bases de service.

Pourquoi une page à part. Ces quatre tables reviennent dans les 15 schémas métier. Les répéter dans chaque MCD les noierait dans le domaine, alors qu'elles relèvent d'une décision d'architecture unique. Les MCD par base ne montrent donc que le domaine ; le socle ci-dessous s'y ajoute systématiquement.

1. Publication — outbox_events

Un service ne publie jamais directement sur Kafka depuis sa logique métier : il écrit l'événement dans sa propre base, dans la même transaction que le changement d'état, et un relais publie ensuite. Sans cela, un crash entre le commit et la publication perd l'événement — ou publie un événement dont le changement d'état a été annulé.

create table outbox_events (
    id           bigserial primary key,
    event_id     varchar(40)  not null unique,
    event_type   varchar(100) not null,
    subject      varchar(50)  not null,
    payload      text         not null,
    occurred_at  timestamptz  not null,
    published    boolean      not null default false,
    published_at timestamptz
);
create index idx_outbox_pending on outbox_events (published, id);

Forme de référence · db/changelog/*_create_outbox_events.sql · l'index partiel sur (published, id) est ce qui rend le balayage du relais constant quel que soit le volume publié.

Écart à corriger dans les schémas modélisés. Les SVG des fiches de service décrivent un outbox_events avec aggregate_id et created_at, là où le code porte subject, occurred_at et un event_id unique. Le code fait foi : c'est la forme réellement créée dans les bases. Les diagrammes des fiches sont à réaligner.

2. Idempotence de consommation — processed_events

Kafka garantit l'at-least-once : un même événement sera parfois livré deux fois. Le consommateur mémorise donc les identifiants déjà traités et ignore les rejeux.

create table processed_events (
    event_id     varchar(40) primary key,
    processed_at timestamptz not null
);

Forme de référence · svc-medical-care

Ne pas confondre avec inbox_events. Douze schémas modélisés portent une table inbox_events (id, event_type, aggregate_id, payload, processed, processed_at). Ce n'est pas un autre nom du même objet : elle stocke la charge utile reçue, quand processed_events ne mémorise que l'identifiant traité. Recommandation : garder processed_events — déduplication pure, volume constant, aucune donnée dupliquée — et ne matérialiser une vraie table d'entrée que là où un rejeu local du payload est réellement requis.

3. Idempotence des commandes — idempotency_keys

Côté API, un client qui rejoue un POST (réseau coupé, bouton cliqué deux fois) ne doit pas créer deux dossiers. La clé fournie par l'appelant est mémorisée avec une empreinte de la requête et la ressource produite.

create table idempotency_keys (
    key          varchar(80) primary key,
    fingerprint  integer     not null,
    claim_number varchar(15) not null,
    created_at   timestamptz not null
);

Forme de référence · svc-claims · la colonne de référence produite est propre à chaque service.

L'empreinte sert à distinguer deux cas : même clé, même requête → renvoyer la ressource déjà créée ; même clé, requête différente → refuser, car le client réutilise une clé pour autre chose.

4. Projections locales — known_<entité>

Un service a souvent besoin d'une donnée détenue par un autre — le numéro de dossier, le nom d'une compagnie. Deux mauvaises réponses : l'appel synchrone à chaque lecture (couplage temporel), ou la table maître dupliquée (deux sources de vérité). La bonne réponse est une copie locale en lecture seule, alimentée par événements et nommée explicitement.

create table known_claims (
    claim_number      varchar(15)  primary key,
    victim_first_name varchar(100) not null,
    victim_last_name  varchar(100) not null,
    opened_at         timestamptz  not null,
    registered_at     timestamptz  not null
);

Forme de référence · svc-medical-care

Le préfixe known_ est le cœur de la convention. Il dit, dans le nom même de la table, « je ne suis pas maître de cette donnée ». Un développeur qui voudrait y écrire voit immédiatement qu'il fait fausse route, et le chantier de reprise voit tout aussi immédiatement qu'il ne doit jamais y charger de données legacy : une projection se reconstruit par rejeu d'événements, elle ne se migre pas.

5. Créées par l'outil — databasechangelog

Liquibase crée et maintient databasechangelog et databasechangeloglock. Elles ne se déclarent pas, mais elles ont un rôle direct dans la reprise : le pipeline de migration lit databasechangelog pour vérifier que la base cible est bien à la version de schéma attendue avant de charger quoi que ce soit, et s'arrête proprement en cas d'écart. C'est ce contrôle qui transforme « le schéma cible est censé exister » en fait vérifié à l'exécution.

Récapitulatif

TableObligatoire pourÉtat réel
outbox_eventsTout service qui détient un état et publie des événements — soit 14 des 15 services métier. svc-audit-trail en est dépourvu : terminal, il ne publie pas. Les BFF et edges non plus : sans état local, l'outbox n'a pas d'objet.CRÉÉE 14 services
processed_eventsTout service qui consomme des événements.1 service
idempotency_keysTout service exposant une API de commande.1 service
known_<entité>Selon les dépendances de lecture du service.1 service
databasechangelogToutes les bases de service.AUTOMATIQUE

La table qui ne devrait pas être là — audit_logs

Douze schémas modélisés portent une table audit_logs locale (entity_type, entity_id, action, old_value, new_value, performed_by, performed_at), alors que svc-audit-trail centralise la piste d'audit en consommant tous les topics. Les deux ne peuvent pas être « la » piste d'audit exigée par les CDC.

L'arbitrage à rendre :

  • Journal central seul — cohérent avec l'architecture événementielle, mais l'audit dépend alors de la chaîne Kafka : si un événement n'est pas publié, l'action n'est pas tracée.
  • Trace technique locale assumée — la table reste, mais elle est nommée et documentée comme une trace de débogage sans valeur probante, et svc-audit-trail demeure la seule piste opposable.

Recommandation : la seconde, à condition de l'écrire. Une table d'audit dont personne ne sait si elle fait foi est pire que pas de table du tout.