GH GambleHub

Pattern Outbox

Outbox é um pattern arquitetônico em que o serviço de domínio grava uma mudança de negócio e um evento apropriado em uma transação local para o seu armazém. A publicação do evento em um pneu/fila externo é executada de forma assinhrônica por um processo de segurança individual (publisher) que lê a tabela 'outbox' e retransmite as gravações. Esta abordagem elimina a corrida «primeiro no banco de dados, depois no pneu» e permite uma entrega segura, mesmo em casos de falhas.

1) Quando aplicar

Adequado:
  • Microsséries e monólitos modulares com eventos entre contextos.
  • Assegure-se de que o «estado registado ↔ não pode perder-se».
  • Precisamos de idempotidade e reaproveitamento controlado.
Não é adequado:
  • As transações globais rígidas em vários recursos (melhor que o TCS/saga com contratos explícitos) são cruciais.
  • Não há origem de verdade selecionada (o estado não está armazenado onde o evento é gerado).

2) Objetivos e propriedades

Atomic write: gravação de domínio + outbox - em uma transação.
At-least-once publicação: permitindo a repetição, excluindo a perda.
Idempotação dos consumidores: protecção contra suplentes ao lado dos seguidores.
Exactly-once eficiente: obtido com a combinação outbox + idempotent consumer + dedup.
Telemetria clara, correlação de negócios e eventos.

3) Esquema de dados (exemplo)

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) Modelo de transação (aplicação layer)

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

Se a empresa tiver sucesso, o evento no outbox é garantido. Se a aplicação cair depois de uma comitiva, o Pablicher consegue.

5) Pablicher (reader → publisher)

Tarefas:
  • Ler eventos não publicados periodicamente ('published _ at IS NULL' e 'available _ at <= now ()') com batches.
  • Tentar publicar no pneu/fila; ao ter êxito, comemorar «published _ at».
  • Erro: aumentar 'attempts', colocar 'available _ at' para o futuro (exponential backoff), escrever 'erro'.
  • Respeitar limites de tenantes/chaves (fairness), não bloquear .
Pseudocode:
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' exclui a concorrência dos pablishers.

6) Idempotidade e dedução

Do lado do consumidor (Inbox/Idempotency store):
sql
CREATE TABLE inbox (
consumer_name  TEXT,
event_id    UUID,
processed_at  TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);

Algoritmo: ao receber um evento, primeiro «INSERT» em «inbox»; se o conflito de chave já foi processado → «no-op». A seguir, a lógica dos negócios.

«Idempotency-Key» no headers (por exemplo, «event _ id») para que o pneu/corretor/proxy possa filtrar as duplicações.

7) Ordem e causalidade

A ordem local por 'aggregate _ id' é garantida pela triagem de 'occurred _ at' e pela publicação de 'chave'.
Para os pneus logs com particionização - particione com a chave 'aggregate _ id '/' tenant _ id' para garantir que os eventos de uma unidade estejam na mesma partishene.
Se a ordem é crítica, evite as corridas interflexivas do pablicher com uma chave.

8) CDC (Change Data Capture)

Você pode usar o CDC em vez do pablicher ativo: o motor lê o registro de transações da BD e transmite as linhas 'outbox' para o pneu. Os benefícios são a carga mínima de BD, a sequência exata, a falta de meia linha. Contras - Complicação da operação e alinhamento à especificidade do DDC. Ambas as abordagens são validadas; selecione por competência e SLO.

9) Erros, DLQ e redrave

Retryable (rede, limites) - Aumentamos 'attempts', adiamos 'available _ at' (exponential backoff + jitter).
Não-retryable (esquema/contrato) - transferido para DLQ/Dead-Letter Topic com metadados ricos.
Redrave seguro: batch, rate-limit, validação do circuito, prioridade abaixo do tráfego prod.

10) Multi-tenência e limites

Marcas de formatação obrigatórias: «tenant _ id», «place», «region» em «outbox». headers`.
Per-tenant fairness: O pablicher distribui «janelas» de publicações e limites de tentativas por locatários.
Residency: armazene o outbox na mesma região onde os dados de domínio; Publicação interregional - apenas agregados/resumos.

11) Segurança e conformidade

Edição PII em payload/headers sobre a política do tenante/região.
Assinar/encriptar carga útil se o pneu for «alheio».
Auditoria de todas as transições de estado: criado, publicado, erro, redrave.

12) Observabilidade

Métricas:
  • A lista de publicação ('now - accurred _ at' p50/p95/p99).
  • Taxa de sucesso, proporção de erros, distribuição de causas.
  • Tamanho do outbox (em não-publicado), tentativas/segundos.
  • Gráficos para-tenentes throughput e lag.
Tracing:
  • Correlação 'event _ id '/' agregate _ id '/' saga _ id'; span «db-tx», «publish», «retry».
  • Anotações: 'attempt',' backoff _ ms ',' dlq = true '.
Logi:
  • Breves registros de sucesso; detalhes completos para erro/redrave.

13) Testes e caos

Atomicity teste: «caímos» artificialmente após uma transação de domínio da empresa antes da publicação - o evento é obrigado a sair mais tarde.
Duplicate teste: Publicamos o mesmo evento várias vezes - a consoante executa exatamente um efeito (inbox).
Teste Order: conjunto de eventos de um único dispositivo - verificação de sequência/idempotação.
Chaos: corretora falha, aumento da latência de BD, split-brain pablishers, clock-skew.

14) Modelos de configuração (exemplo)

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) Integração com sagas e retraias

Outbox - «transporte de segurança» para os passos da saga: transação local escreve o efeito e comando/evento; a publicação é confiável e dosável.
Políticas de repetição e backoff devem ser alinhadas com 'Retry-After' e Circuito Breaker; Evite «tempestade de retrações».

16) Erros típicos

Escrevendo um evento após uma comitiva de domínio - possível perda de queda.
Sem índice/arquivo em 'outbox' → aumento do atraso na publicação.
Pablicher sem 'SKIP LOCKED' ou sem 'charding' - concorrência e bloqueio.
A falta de idempotidade dos consumidores - duplos e efeitos colaterais.
Mistura de PII sem disfarce em DLQ/logs.
Uma única fila global de publicação sem fairness - um tenente «barulhento» trava todos.
Falta de monitoramento da laje → degradação oculta.

17) Escolha rápida de estratégia

Nível inicial: polling de BD, batch de 100-500, full-jitter backoff, inbox em consoadores.
Alta carga: CDC do diário de transações, charding por 'tenant _ id/agregate _ id', WFQ por locatário.
Ordem rígida por unidade: publicação em série per key (mutex), particionização de top com chave.
Complaens/PII: criptografia payload, redação em DLQ, outbox regional.

18) Folha de cheque antes de vender

  • As alterações de domínio e a gravação em 'outbox' ocorrem em uma transação.
  • O pablicher processa batches, usa 'SKIP LOCKED', backoff com jitter e limites.
  • Os conceituadores são idimpotentes (tabela 'inbox '/registro de dedução).
  • DLQ configurado e redrave seguro.
  • Métricas de laje/erro e alertas nas liminares p95/p99.
  • A ordem da chave está garantida (partituras/série).
  • Arquivo/retenchn 'outbox' e limpeza de gravações publicadas.
  • Políticas PII e auditoria de transições de estados.
  • Testes de queda entre commitas e publicações, duplicados e ordem.
  • Documentação dos contratos do evento (esquemas/versões/compatibilidade).

Conclusão

O Outbox-Pattern transforma o ligamento «frágil» do «pneu ↔ BD» em uma linha de montagem confiável: fixação atômica do estado, publicação garantida (mesmo que «pelo menos uma vez»), assinantes idimpotentes e redrave controlada. Com a telemetria correta, os limites e a disciplina dos circuitos, ele oferece um comportamento exactly-once prático, reduzindo a complexidade das transações distribuídas e aumentando a resistência do sistema a falhas e cargas de pico.

Contact

Entrar em contacto

Contacte-nos para qualquer questão ou necessidade de apoio.Estamos sempre prontos para ajudar!

Telegram
@Gamble_GC
Iniciar integração

O Email é obrigatório. Telegram ou WhatsApp — opcionais.

O seu nome opcional
Email opcional
Assunto opcional
Mensagem opcional
Telegram opcional
@
Se indicar Telegram — responderemos também por lá.
WhatsApp opcional
Formato: +indicativo e número (ex.: +351XXXXXXXXX).

Ao clicar, concorda com o tratamento dos seus dados.