-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
Título: Add idempotency support with persisted keys
Labels: critical, idempotency, reliability
Prioridade: CRÍTICA
Problema Atual (Ponto 4 do Feedback):
- Ausência de checagens de idempotência
- Risco de duplicidade em retries
Solução Proposta:
-- Tabela de idempotência
CREATE TABLE idempotency_keys (
key VARCHAR(255) PRIMARY KEY,
operation_type VARCHAR(50) NOT NULL,
request_hash VARCHAR(255) NOT NULL,
response_data JSONB,
status VARCHAR(20) NOT NULL, -- PROCESSING, COMPLETED, FAILED
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
INDEX idx_expires_at (expires_at)
);// DataProvider - Idempotency Service
@Service
@Transactional
public class IdempotencyService {
@Transactional
public <T> T executeIdempotent(String idempotencyKey,
String operationType,
String requestHash,
Supplier<T> operation,
Class<T> responseType) {
// 1. Verificar se já existe
Optional<IdempotencyRecord> existing =
idempotencyRepository.findByKey(idempotencyKey);
if (existing.isPresent()) {
IdempotencyRecord record = existing.get();
if (record.getStatus() == IdempotencyStatus.COMPLETED) {
// Retornar resultado cached
return deserializeResponse(record.getResponseData(), responseType);
}
if (record.getStatus() == IdempotencyStatus.PROCESSING) {
throw new OperationInProgressException("Operation already in progress");
}
}
// 2. Marcar como processando
IdempotencyRecord record = IdempotencyRecord.builder()
.key(idempotencyKey)
.operationType(operationType)
.requestHash(requestHash)
.status(IdempotencyStatus.PROCESSING)
.expiresAt(LocalDateTime.now().plus(TTL))
.build();
idempotencyRepository.save(record);
try {
// 3. Executar operação
T result = operation.get();
// 4. Marcar como concluído e salvar resultado
record.setStatus(IdempotencyStatus.COMPLETED);
record.setResponseData(serializeResponse(result));
record.setCompletedAt(LocalDateTime.now());
idempotencyRepository.save(record);
return result;
} catch (Exception e) {
// 5. Marcar como falha
record.setStatus(IdempotencyStatus.FAILED);
idempotencyRepository.save(record);
throw e;
}
}
}
// Uso nos Use Cases
public class DepositUseCase {
public WalletTransaction execute(String userId, Money amount, String idempotencyKey) {
String requestHash = calculateHash(userId, amount);
return idempotencyService.executeIdempotent(
idempotencyKey,
"DEPOSIT",
requestHash,
() -> transactionalWalletService.executeDeposit(userId, amount),
WalletTransaction.class
);
}
}Tarefas:
-
Criar tabela idempotency_keys
-
Implementar IdempotencyService no dataprovider
-
Adicionar idempotencyKey em todos os requests
public record DepositRequest( @NotNull BigDecimal amount, @NotBlank String currency, @NotBlank String idempotencyKey // NOVO CAMPO ) {}
-
Configurar TTL para limpeza automática
-
Testes de duplicação/retry
-
Métricas de operações idempotentes