Context Imutável
O SagaContext é o objeto que viaja entre os steps. Ele deve ser tratado como imutável para garantir previsibilidade e auditoria.
Por que Imutabilidade?
Sem Imutabilidade ( Errado)
public class PixContext {
private String blockId; // Mutável
public void setBlockId(String id) {
this.blockId = id; // <DocIcon name="alert" /> Mutação!
}
}
// No step...
private PixContext blockBalance(PixContext ctx) {
String blockId = balanceService.block(...);
ctx.setBlockId(blockId); // <DocIcon name="alert" /> Altera o objeto original
return ctx;
}
Problemas:
- Difícil rastrear quando o valor mudou
- Race conditions em cenários concorrentes
- Compensação pode ver valor inconsistente
- Log de auditoria não reflete estado real
Com Imutabilidade ( Correto)
public record PixContext(
String transactionId,
String dictKey,
BigDecimal amount,
String blockId // Imutável
) {
public PixContext withBlockId(String id) {
return new PixContext(transactionId, dictKey, amount, id);
}
}
// No step...
private PixContext blockBalance(PixContext ctx) {
String blockId = balanceService.block(...);
return ctx.withBlockId(blockId); // <DocIcon name="check" /> Retorna nova instância
}
Benefícios:
- Cada step recebe um snapshot do estado
- Fácil de debugar e auditar
- Thread-safe por design
- Compensação tem acesso ao estado correto
Padrões de Implementação
Usando Java Records (Recomendado)
public record OrderContext(
// Dados de entrada (imutáveis desde o início)
String orderId,
String customerId,
List<OrderItem> items,
BigDecimal totalAmount,
// Outputs dos steps (populados durante execução)
String stockReservationId,
String paymentIntentId,
String shippingLabelId
) {
// Builders para cada output
public OrderContext withStockReservationId(String id) {
return new OrderContext(
orderId, customerId, items, totalAmount,
id, paymentIntentId, shippingLabelId
);
}
public OrderContext withPaymentIntentId(String id) {
return new OrderContext(
orderId, customerId, items, totalAmount,
stockReservationId, id, shippingLabelId
);
}
public OrderContext withShippingLabelId(String id) {
return new OrderContext(
orderId, customerId, items, totalAmount,
stockReservationId, paymentIntentId, id
);
}
}
Usando Lombok @With
@Value
@With
public class OrderContext {
String orderId;
String customerId;
List<OrderItem> items;
BigDecimal totalAmount;
String stockReservationId;
String paymentIntentId;
String shippingLabelId;
}
// Uso
return ctx.withStockReservationId(id);
Usando Builder Pattern
@Value
@Builder(toBuilder = true)
public class OrderContext {
String orderId;
String customerId;
List<OrderItem> items;
BigDecimal totalAmount;
String stockReservationId;
String paymentIntentId;
String shippingLabelId;
}
// Uso
return ctx.toBuilder()
.stockReservationId(id)
.build();
Fluxo do Context
┌─────────────────────────────────────────────────────────────────┐
│ Saga Execution │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Context v0 ─────► Step 1 ─────► Context v1 │
│ {orderId, ...} reserve() {orderId, stockId, ...} │
│ │
│ Context v1 ─────► Step 2 ─────► Context v2 │
│ {stockId, ...} charge() {stockId, paymentId, ...} │
│ │
│ Context v2 ─────► Step 3 ─────► Context v3 (final) │
│ {paymentId,...} ship() {paymentId, trackingId,...} │
│ │
└─────────────────────────────────────────────────────────────────┘
Compensação (se step 3 falhar):
│ Context v2 ─────► Comp 2 ─────► refund(paymentId) │
│ Context v1 ─────► Comp 1 ─────► release(stockId) │
Persistência do Context
O Sagaweaw serializa o context como JSONB no PostgreSQL:
SELECT id, name, status, context
FROM sagas
WHERE id = 'saga-48291';
{
"orderId": "ORD-9912",
"customerId": "CUST-001",
"items": [...],
"totalAmount": 299.90,
"stockReservationId": "STK-123",
"paymentIntentId": "pi_abc123",
"shippingLabelId": null
}
Serialização
Use tipos serializáveis (String, BigDecimal, List, Map). Evite objetos complexos ou referências circulares.
Erros Comuns
Coleções Mutáveis
// ERRADO
public record OrderContext(
List<String> processedIds // ArrayList é mutável!
) {}
ctx.processedIds().add("new-id"); // <DocIcon name="alert" /> Mutação!
Coleções Imutáveis
// CORRETO
public record OrderContext(
List<String> processedIds
) {
public OrderContext withAddedId(String id) {
var newList = new ArrayList<>(processedIds);
newList.add(id);
return new OrderContext(List.copyOf(newList));
}
}