Quickstart
Configure o Sagaweaw em seu projeto Spring Boot em 5 minutos.
1. Adicione a Dependência
Maven
<dependency>
<groupId>dev.sagaweaw</groupId>
<artifactId>sagaweaw-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Flyway é obrigatório para criação automática do schema -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Se usar PostgreSQL, adicione também: -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<scope>runtime</scope>
</dependency>
Gradle
implementation 'dev.sagaweaw:sagaweaw-spring-boot-starter:1.0.0'
runtimeOnly 'org.flywaydb:flyway-core'
runtimeOnly 'org.flywaydb:flyway-database-postgresql' // se usar PostgreSQL
2. Configure o Banco de Dados
O Sagaweaw suporta PostgreSQL, MySQL 8+ e H2 — o Flyway seleciona automaticamente a migration correta via {vendor}. Adicione no application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/sagaweaw # ou mysql:// ou h2
username: ${DB_USER}
password: ${DB_PASSWORD}
sagaweaw:
enabled: true
observability:
enabled: true
token: ${SAGAWEAW_TOKEN} # obrigatório para acessar /api/sagas e /api/dead-letters
retry:
default-policy: exponential
max-attempts: 3
O starter executa as migrations via Flyway ao iniciar. As tabelas sagas, saga_steps, saga_events, outbox_messages e dead_letters são criadas automaticamente para o banco detectado.
Sem sagaweaw.observability.token configurado, todos os endpoints de observabilidade retornam 403. Isso é intencional.
3. Crie o Context
O SagaContext é um objeto imutável que viaja entre os steps:
public record PixContext(
String transactionId,
String dictKey,
BigDecimal amount,
String payerId,
String payeeId,
// Outputs dos steps (populados durante execução)
String validationToken,
String blockId,
String bacenProtocol
) {
// Builder para criar novas versões imutáveis
public PixContext withValidationToken(String token) {
return new PixContext(
transactionId, dictKey, amount, payerId, payeeId,
token, blockId, bacenProtocol
);
}
public PixContext withBlockId(String id) {
return new PixContext(
transactionId, dictKey, amount, payerId, payeeId,
validationToken, id, bacenProtocol
);
}
}
4. Defina a Saga
@Saga("pix-payment")
public class PixPaymentSaga implements SagaDefinition<PixContext> {
private final DictService dictService;
private final BalanceService balanceService;
private final BacenService bacenService;
@Override
public SagaFlow<PixContext> define(SagaBuilder<PixContext> saga) {
return saga
// Step 1: Validar chave PIX
.step("validate-dict")
.invoke(this::validateDict)
.compensate(this::invalidateDict)
// Step 2: Bloquear saldo
.step("block-balance")
.invoke(this::blockBalance)
.compensate(this::unblockBalance)
// Step 3: Transmitir ao BACEN (PIVOT - sem compensação)
.step("transmit-to-bacen")
.invoke(this::transmitToBacen)
.retry(exponential(3, Duration.ofSeconds(1)))
.build();
}
// --- Invoke Methods ---
private PixContext validateDict(PixContext ctx) {
String token = dictService.validate(ctx.dictKey());
return ctx.withValidationToken(token);
}
private PixContext blockBalance(PixContext ctx) {
String blockId = balanceService.block(ctx.payerId(), ctx.amount());
return ctx.withBlockId(blockId);
}
private PixContext transmitToBacen(PixContext ctx) {
bacenService.transmit(ctx.transactionId(), ctx.amount());
return ctx;
}
// --- Compensate Methods ---
private void invalidateDict(PixContext ctx) {
dictService.invalidate(ctx.validationToken());
}
private void unblockBalance(PixContext ctx) {
balanceService.unblock(ctx.blockId());
}
}
5. Dispare a Saga
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
private final SagaManager sagaManager;
@PostMapping("/pix")
public ResponseEntity<SagaResponse> createPixPayment(
@RequestBody PixRequest request
) {
PixContext context = new PixContext(
UUID.randomUUID().toString(),
request.getDictKey(),
request.getAmount(),
request.getPayerId(),
request.getPayeeId(),
null, null, null // Outputs iniciam nulos
);
SagaExecution execution = sagaManager.start(PixPaymentSaga.class, context);
return ResponseEntity.accepted().body(
new SagaResponse(execution.sagaId())
);
}
}
6. Acesse a Observability API
Com a aplicação rodando, consulte suas sagas via REST:
# Listar sagas (últimas 50)
curl -H "Authorization: Bearer $SAGAWEAW_TOKEN" \
http://localhost:8080/api/sagas
# Métricas agregadas
curl -H "Authorization: Bearer $SAGAWEAW_TOKEN" \
http://localhost:8080/api/sagas/metrics
Você também pode usar o header X-Sagaweaw-Token: <token> como alternativa ao Bearer.
Veja todos os endpoints disponíveis em Observability API →.
Verificação
Sua estrutura deve estar assim:
src/main/java/
├── payment/
│ ├── PixPaymentSaga.java # Definição da saga — junto ao domínio
│ ├── PixContext.java # Context imutável
│ ├── PaymentController.java # Endpoint de disparo
│ └── service/
│ ├── DictService.java
│ ├── BalanceService.java
│ └── BacenService.java
Mantenha PixPaymentSaga no mesmo pacote dos serviços que ela usa. Evite criar um pacote sagas/ separado — isso dificulta entender o contexto ao ler o código.
Próximos Passos
- Tipos de Steps → — COMPENSABLE, PIVOT, RETRIABLE
- Ciclo de Vida → — Estados da saga
- AI Prompt → — Gere sagas com IA