Pular para o conteúdo principal

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));
}
}

Próximos Passos