IdempotencyKey
Chave para garantir que a mesma saga não execute duas vezes.
Conceito
A IdempotencyKey evita que operações duplicadas criem múltiplas sagas. Se você disparar a mesma saga com a mesma chave, a segunda chamada retornará a saga existente.
Uso
Ao Disparar
@PostMapping("/payments")
public ResponseEntity<SagaResponse> createPayment(
@RequestBody PaymentRequest request,
@RequestHeader("Idempotency-Key") String idempotencyKey
) {
PaymentContext context = PaymentContext.from(request);
SagaExecution execution = sagaManager.start(
PaymentProcessingSaga.class,
context,
IdempotencyKey.of(idempotencyKey) // ← Chave de idempotência
);
return ResponseEntity.accepted().body(
new SagaResponse(execution.sagaId(), execution.idempotent())
);
}
Padrões de Chave
Request ID (Recomendado)
// Cliente envia um UUID único
IdempotencyKey.of(request.getRequestId())
Composição de Dados
// Combina dados do negócio
IdempotencyKey.of(
"payment:" + request.getOrderId() + ":" + request.getAmount()
)
Hash
// Hash de todo o payload
String hash = DigestUtils.sha256Hex(objectMapper.writeValueAsString(request));
IdempotencyKey.of(hash)
Comportamento
Primeira Chamada
POST /payments
Idempotency-Key: req-123
→ Saga criada com id=saga-001
→ Status: STARTED
Segunda Chamada (mesma chave)
POST /payments
Idempotency-Key: req-123
→ Retorna saga existente id=saga-001
→ Status: EXECUTING (ou COMPLETED se já terminou)
Armazenamento
A chave é armazenada na coluna idempotency_key da tabela sagas:
CREATE TABLE sagas (
...
idempotency_key VARCHAR(255) UNIQUE,
...
);
O índice UNIQUE garante que duas sagas não podem ter a mesma chave.
Considerações
TTL
A chave persiste enquanto a saga existir. Para operações que podem ser repetidas após um tempo, considere incluir um timestamp na chave:
// Permite retry após 24h
LocalDate today = LocalDate.now();
IdempotencyKey.of("payment:" + orderId + ":" + today)
Colisões
Evite chaves genéricas que podem colidir:
// <DocIcon name="x" /> Ruim - pode colidir
IdempotencyKey.of(customerId)
// <DocIcon name="check" /> Bom - específico
IdempotencyKey.of(customerId + ":" + orderId + ":" + Instant.now().toEpochMilli())
API Completa
public record IdempotencyKey(String value) {
public static IdempotencyKey of(String value) {
return new IdempotencyKey(value);
}
public static IdempotencyKey generate() {
return new IdempotencyKey(UUID.randomUUID().toString());
}
public static IdempotencyKey fromRequest(HttpServletRequest request) {
String header = request.getHeader("Idempotency-Key");
return header != null ? of(header) : generate();
}
}