GH GambleHub

Modèle Outbox

Outbox est un modèle architectural dans lequel le service de domaine enregistre le changement d'entreprise et l'événement correspondant dans une transaction locale dans son stockage. La publication de l'événement sur le bus/file d'attente externe est effectuée de manière asynchrone par un processus sécurisé distinct (publisher) qui lit la table « outbox » et relaie les enregistrements. Cette approche élimine la course « d'abord dans la base de données, puis dans le pneu » et assure une livraison fiable même en cas de défaillance.

1) Quand appliquer

Convient :
  • Microservices et monolithes modulaires avec événements entre les contextes.
  • Vous devez vous assurer que « l'état enregistré ↔ l'événement ne peut pas se perdre ».
  • Il faut de l'idempotence et une réadmission contrôlée.
Ne convient pas :
  • Les transactions mondiales rigides sur plusieurs ressources sont critiques (mieux que les STS/sagas avec des contrats explicites).
  • Il n'y a pas de source de vérité dédiée (l'état n'est pas stocké là où l'événement est généré).

2) Objectifs et propriétés

Écriture atomique : enregistrement de domaine + outbox - en une seule transaction.
Publication at-least-once : nous admettons la répétition, nous excluons la perte.
Idempotence des consommateurs : protection contre les prises du côté des abonnés.
Efficace exactly-once : obtenu par la combinaison outbox + idempotent consumer + dedup.
Télémétrie claire : corrélation des opérations et des événements commerciaux.

3) Schéma de données (exemple)

sql
-- Domain table (example: orders)
CREATE TABLE orders (
id       UUID PRIMARY KEY,
tenant_id    TEXT NOT NULL,
status     TEXT NOT NULL,
total_amount  NUMERIC(12,2) NOT NULL,
updated_at   TIMESTAMP NOT NULL DEFAULT now()
);

-- Outbox
CREATE TABLE outbox (
id       UUID PRIMARY KEY,        -- event_id aggregate_type TEXT NOT NULL,          -- 'order'
aggregate_id  UUID NOT NULL,          -- order_id tenant_id    TEXT NOT NULL,
type      TEXT NOT NULL,          -- 'OrderCreated'
payload JSONB NOT NULL, -- serialized headers event JSONB NOT NULL DEFAULT '{}':: jsonb,
occurred_at TIMESTAMP NOT NULL, -- time in domain transaction available_at TIMESTAMP NOT NULL, -- earliest publish time (backoff)
published_at TIMESTAMP, - is filled by the attempts INT NOT NULL DEFAULT 0,
error      TEXT
);

CREATE INDEX ON outbox (available_at) WHERE published_at IS NULL;
CREATE INDEX ON outbox (tenant_id, available_at) WHERE published_at IS NULL;

4) Modèle transactionnel (application layer)

pseudo begin tx domainChange () # INSERT/UPDATE in domain table insert into outbox (event) # event with aggregate/tenant commit tx keys

Si commit réussit, l'événement outbox est garanti. Si l'application tombe après le commit, le pub rattrapera.

5) Pablisher (lecteur → éditeur)

Tâches :
  • Lisez périodiquement les événements non publiés ('published _ at IS NULL'et' available _ at <= now () ') par trampoline.
  • Essayer de publier dans le bus/file d'attente ; si vous réussissez, notez 'published _ at'.
  • En cas d'erreur, agrandir 'attempts', mettre 'available _ at' pour l'avenir (backoff exponentiel), écrire 'error'.
  • Respecter les limites des tenants/clés (fairness), ne pas bloquer le jeu.
Pseudo-code :
pseudo loop:
events = select from outbox where published_at is null and available_at <= now()
order by occurred_at limit BATCH_SIZE for update skip locked

for e in events:
try:
broker. publish(topicFor(e), serialize(e. payload), headers(e))
markPublished(e. id, now())
except Retryable:
backoff = computeBackoff(e. attempts)
reschedule(e. id, now()+backoff, attempts+1, last_error)
except NonRetryable:
moveToDLQ (e) or markError (e) # by sleep (POLL_INTERVAL) policy
💡 « FOR UPDATE SKIP LOCKED » exclut la concurrence des pubs.

6) Idempotence et déduplication

Côté consommateur (Inbox/Idempotency store) :
sql
CREATE TABLE inbox (
consumer_name  TEXT,
event_id    UUID,
processed_at  TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);

Algorithme : si vous recevez un événement, vous essayez d'abord 'INSERT' dans 'inbox' ; si le conflit de clé est un événement déjà traité → « no-op ». Ensuite, la logique d'entreprise.

Du côté du pablisher : 'Idempotency-Key' dans les headers (par exemple, 'event _ id') afin que le bus/broker/proxy puisse filtrer les doublons.

7) Ordre et causalité

L'ordre local par 'aggregate _ id' est fourni en triant 'occurred _ at'et en publiant « par clé ».
Pour les bus logs à lot, entrez la clé 'aggregate _ id '/' tenant _ id'pour que les événements d'une unité soient dans le même lot.
Si l'ordre est critique, évitez les courses interurbaines de pablisher sur une seule clé.

8) CDC (Change Data Capture)

Au lieu d'un pablisher actif, vous pouvez utiliser CDC : le moteur lit le journal des transactions de la base de données et diffuse les lignes « outbox » dans le bus. Les avantages sont la charge minimale sur l'OBD, la séquence exacte, l'absence de polling. Les inconvénients sont la complexité de l'opération et le lien avec les caractéristiques de la base de données. Les deux approches sont validées ; choisissez par compétence et SLO.

9) Erreurs, DLQ et redrive

Retryable (réseau, limites) : Augmentez 'attempts', reportez 'available _ at' (backoff exponentiel + jitter).
Non-rétractable (schéma/contrat non validé) - Nous transférons dans DLQ/Dead-Letter Topic avec des métadonnées riches.
Redrive sécurisé : batchi, rate-limit, validation de schéma, priorité inférieure au trafic pro.

10) Multi-tenance et limites

Les balises obligatoires sont 'tenant _ id', 'plan', 'region' dans 'outbox. headers`.
Per-tenant fairness : le pablisher distribue les « fenêtres » des publications et les limites des tentatives aux locataires.
Residency : Stockez l'outbox dans la même région où les données de domaine ; publication interrégionale - agrégats/résumés uniquement.

11) Sécurité et conformité

Édition PII dans payload/headers sur la politique tenante/région.
Signature/chiffrement de la charge utile si le bus est « étranger ».
Audit de toutes les transitions d'état : création, publication, erreur, redrave.

12) Observabilité

Métriques :
  • Publication ('maintenant - occurred_at' p50/p95/p99).
  • Proportion de succès, proportion d'erreurs, répartition des causes.
  • Taille outbox (non publiée), tentatives/s.
  • Graphiques per-tenant throughput et lag.
Tracing :
  • Corrélation 'event _ id '/' aggregate _ id '/' saga _ id' ; les spans « db-tx », « publish », « retry ».
  • Annotations : 'attempt', 'backoff _ ms',' dlq = true '.
Logs :
  • De brèves notes de succès ; détails complets sur l'erreur/redrave.

13) Test et chaos

Test d'atomicité : nous « tombons » artificiellement après la commit de la transaction de domaine avant la publication - l'événement est obligé de quitter plus tard.
Test duplicate : nous publions le même événement plusieurs fois - le consumer exécute exactement un seul effet (inbox).
Test d'ordre : un paquet d'événements par agrégat - vérification de la séquence/idempotence.
Chaos : refus du courtier, augmentation de la latence de la BD, split-brain des pablishers, clock-skew.

14) Modèles de configuration (exemple)

yaml outbox:
poll_interval_ms: 200 batch_size: 200 order_by: occurred_at backoff:
strategy: exponential_full_jitter initial_ms: 250 max_ms: 10_000 max_attempts: 20 fairness:
per_tenant_parallelism: 4 per_key_serial: true

publisher:
rate_limit_per_sec: 500 headers:
idempotency_key: event_id schema_version: v3 dlq:
enabled: true topic: myapp. events. dlq include_metadata:
- error
- attempts
- source_table
- tenant_id
- aggregate_id

15) Intégration avec les sagas et les retraits

Outbox - « transport de sécurité » pour les étapes de la saga : une transaction locale écrit un effet et une commande/événement ; publication - fiable et dosable.
Les politiques de répétition et de backoff doivent être harmonisées avec « Retry-After » et Circuit Breaker ; évitez la « tempête des rétrogrades ».

16) Erreurs typiques

Ils écrivent un événement après la commite de l'état du domaine - une perte possible lors de la chute.
Il n'y a pas d'index/archive dans 'outbox' → augmentation du délai de publication.
Pablisher sans 'SKIP LOCKED' ou sans chardonnages - concurrence et blocages.
L'absence d'idempotence chez les consommateurs est une prise et des effets secondaires.
Mélange de PII sans masquage dans les DLQ/logs.
Une seule file d'attente mondiale de publication sans fairness - un tenant « bruyant » freine tout le monde.
L'absence de surveillance de la lagune → les dégradations latentes.

17) Sélection rapide de la stratégie

Niveau de départ : polling de la base de données, batchi 100-500, full-jitter backoff, inbox chez les consumers.
Charge élevée : CDC du journal des transactions, Charding par 'tenant _ id/aggregate _ id', WFQ par locataire.
Ordre strict par agrégat : publication en série par clé (mutex), lot de la hache par clé.
Conformité/PII : cryptage payload, édition en DLQ, box régional.

18) Chèque-liste avant la vente

  • Les modifications de domaine et l'écriture dans 'outbox' se produisent dans la même transaction.
  • Pablisher traite les batchs, utilise « SKIP LOCKED », backoff avec jitter et limites.
  • Les consumers sont idempotentes (tableau « inbox »/journal de dedup).
  • DLQ et redrive sécurisé sont configurés.
  • Métriques de lagune/erreur et alertes aux seuils p95/p99.
  • L'ordre par clé est garanti (lots/sérialité).
  • Archive/Retenshn'outbox 'et nettoyage des enregistrements publiés.
  • Politiques PII et vérification des transitions d'états.
  • Tests de chute entre commit et publication, doublons et ordre.
  • Documentation des contrats de l'événement (schémas/versions/interopérabilité).

Conclusion

Le modèle Outbox transforme le lien « fragile » « BD ↔ pneu » en un convoyeur fiable : fixation atomique de l'état, publication garantie (quoique « au moins une fois »), abonnés idempotent et redrave contrôlée. Avec la télémétrie appropriée, les limites et la discipline des circuits, il donne un comportement exactly-once pratique, réduisant la complexité des transactions distribuées et améliorant la résistance du système aux pannes et aux charges de pointe.

Contact

Prendre contact

Contactez-nous pour toute question ou demande d’assistance.Nous sommes toujours prêts à vous aider !

Telegram
@Gamble_GC
Commencer l’intégration

L’Email est obligatoire. Telegram ou WhatsApp — optionnels.

Votre nom optionnel
Email optionnel
Objet optionnel
Message optionnel
Telegram optionnel
@
Si vous indiquez Telegram — nous vous répondrons aussi là-bas.
WhatsApp optionnel
Format : +code pays et numéro (ex. +33XXXXXXXXX).

En cliquant sur ce bouton, vous acceptez le traitement de vos données.