Pular para o conteúdo principal

Database Schema

O Sagaweaw persiste todo o estado em PostgreSQL, MySQL 8+ ou H2. O Flyway seleciona automaticamente a migration correta com base no banco detectado ({vendor}). Aqui está o schema completo.


Diagrama de Entidades

┌─────────────────┐ ┌─────────────────┐
│ sagas │───────│ saga_steps │
└─────────────────┘ └─────────────────┘

│ ┌─────────────────┐
├────────│ saga_events │
│ └─────────────────┘

│ ┌─────────────────┐
├────────│ outbox_messages │
│ └─────────────────┘

│ ┌─────────────────┐
└────────│ dead_letters │
└─────────────────┘

Tabelas

sagas

Tabela principal que armazena o estado de cada saga em execução.

CREATE TABLE sagas (
id VARCHAR(36) NOT NULL,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL,
context_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
completed_at TIMESTAMP(6) WITH TIME ZONE,
idempotency_key VARCHAR(255),
version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);

CREATE UNIQUE INDEX idx_sagas_idempotency ON sagas (idempotency_key)
WHERE idempotency_key IS NOT NULL;
CREATE INDEX idx_sagas_status ON sagas (status);
CREATE INDEX idx_sagas_name ON sagas (name);
CREATE INDEX idx_sagas_created_at ON sagas (created_at);
ColunaTipoDescrição
idVARCHAR(36)UUID da saga
nameVARCHARNome da saga (ex: "pix-payment")
statusVARCHARSTARTED, EXECUTING, COMPLETED, COMPENSATING, COMPENSATED, FAILED
context_jsonJSONBContexto serializado (imutável entre steps)
versionINTEGERVersão para optimistic locking
idempotency_keyVARCHARChave para evitar execução duplicada

saga_steps

Estado de cada etapa dentro de uma saga.

CREATE TABLE saga_steps (
id VARCHAR(36) NOT NULL,
saga_id VARCHAR(36) NOT NULL,
step_name VARCHAR(255) NOT NULL,
step_order INTEGER NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
attempt INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
next_retry_at TIMESTAMP(6) WITH TIME ZONE,
last_error TEXT,
error_trace TEXT,
input_payload JSONB,
output_payload JSONB,
executed_at TIMESTAMP(6) WITH TIME ZONE,
completed_at TIMESTAMP(6) WITH TIME ZONE,
duration_ms BIGINT,
PRIMARY KEY (id),
CONSTRAINT fk_saga_steps_saga FOREIGN KEY (saga_id) REFERENCES sagas (id)
);

CREATE INDEX idx_saga_steps_saga_id ON saga_steps (saga_id);
CREATE INDEX idx_saga_steps_retry ON saga_steps (status, next_retry_at);
ColunaTipoDescrição
step_nameVARCHARNome do step (ex: "validate-dict")
step_orderINTEGEROrdem de execução (0, 1, 2…)
statusVARCHARPENDING, EXECUTING, COMPLETED, FAILED, COMPENSATING, COMPENSATED
attemptINTEGERTentativa atual
max_attemptsINTEGERMáximo de tentativas (habilita display "tentativa X/Y")
next_retry_atTIMESTAMPQuando o próximo retry está agendado
last_errorTEXTMensagem do último erro
error_traceTEXTStack trace completo do último erro
input_payloadJSONBContexto serializado na entrada do step
output_payloadJSONBContexto serializado na saída (usado pelo compensador)
duration_msBIGINTDuração da execução em milissegundos

saga_events

Log imutável de eventos para auditoria completa. Só recebe INSERT, nunca UPDATE.

CREATE TABLE saga_events (
id VARCHAR(36) NOT NULL,
saga_id VARCHAR(36) NOT NULL,
step_name VARCHAR(255),
event_type VARCHAR(100) NOT NULL,
payload JSONB,
created_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_saga_events_saga FOREIGN KEY (saga_id) REFERENCES sagas (id)
);

CREATE INDEX idx_saga_events_saga_id ON saga_events (saga_id);
CREATE INDEX idx_saga_events_created ON saga_events (created_at);
ColunaTipoDescrição
event_typeVARCHARSAGA_STARTED, STEP_STARTED, STEP_COMPLETED, STEP_FAILED, COMPENSATION_STARTED, etc.
payloadJSONBDados do evento (mensagem de erro, duração, etc.)

Consulte o histórico de eventos de uma saga via API:

GET /api/sagas/{id}/events

outbox_messages

Mensagens para o padrão Transactional Outbox. Sem FK em saga_id para suportar arquivamento de sagas.

CREATE TABLE outbox_messages (
id VARCHAR(36) NOT NULL,
saga_id VARCHAR(255) NOT NULL,
step_name VARCHAR(255),
topic VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
headers JSONB,
published BOOLEAN NOT NULL DEFAULT FALSE,
publish_attempts INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
published_at TIMESTAMP(6) WITH TIME ZONE,
PRIMARY KEY (id)
);

CREATE INDEX idx_outbox_unpublished ON outbox_messages (published, created_at);
ColunaTipoDescrição
topicVARCHARTópico Kafka destino
headersJSONBHeaders adicionais da mensagem
publish_attemptsINTEGERTentativas de publicação
publishedBOOLEANSe já foi publicado

dead_letters

Sagas que esgotaram as tentativas e precisam de intervenção manual. Sem FK em saga_id para persistir o forense mesmo após remoção da saga.

CREATE TABLE dead_letters (
id VARCHAR(36) NOT NULL,
saga_id VARCHAR(255) NOT NULL,
step_name VARCHAR(255) NOT NULL,
error_message TEXT,
error_trace TEXT,
context_snapshot TEXT,
created_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
reprocessed BOOLEAN NOT NULL DEFAULT FALSE,
reprocessed_at TIMESTAMP(6) WITH TIME ZONE,
reprocessed_by VARCHAR(255),
PRIMARY KEY (id)
);

CREATE INDEX idx_dead_letters_saga_id ON dead_letters (saga_id);
CREATE INDEX idx_dead_letters_unreproc ON dead_letters (reprocessed, created_at);
ColunaTipoDescrição
error_traceTEXTStack trace completo
context_snapshotTEXTSnapshot do contexto da saga no momento da falha
reprocessedBOOLEANSe foi reprocessado via API ou manualmente
reprocessed_byVARCHARIdentificação de quem reprocessou ("api" ou usuário)

Migrations por Banco

O Sagaweaw usa Flyway com o placeholder {vendor} para selecionar a migration correta automaticamente:

resources/
└── db/migration/sagaweaw/
├── postgresql/
│ └── V1__sagaweaw_schema.sql ← JSONB, TIMESTAMP(6) WITH TIME ZONE
├── mysql/
│ └── V1__sagaweaw_schema.sql ← JSON, DATETIME(6), TINYINT(1)
└── h2/
└── V1__sagaweaw_schema.sql ← JSON, TIMESTAMP WITH TIME ZONE

Nenhuma configuração extra é necessária — basta apontar o datasource.url para o banco desejado.


Próximos Passos