Desafios das Transações Distribuídas
Em microsserviços, uma única operação de negócio pode abranger múltiplos serviços. Como cada serviço possui seu próprio banco de dados, não é possível usar transações ACID tradicionais.
flowchart LR
Order["1. Serviço de Pedidos<br/>Criar pedido"]
Inventory["2. Serviço de Estoque<br/>Reduzir estoque"]
Payment["3. Serviço de Pagamento<br/>Processar pagamento"]
Shipping["4. Serviço de Entrega<br/>Agendar entrega"]
Order --> Inventory --> Payment --> Shipping
Question["❓ E se falhar em algum lugar?<br/>Fazer rollback de tudo?"]
Two-Phase Commit (2PC)
Protocolo onde todos os participantes confirmam estar prontos antes de fazer commit simultaneamente.
sequenceDiagram
participant C as Coordenador
participant A as Participante A
participant B as Participante B
C->>A: 1. Prepare
C->>B: 1. Prepare
A-->>C: 2. Ready
B-->>C: 2. Ready
C->>A: 3. Commit
C->>B: 3. Commit
A-->>C: 4. Done
B-->>C: 4. Done
Problemas do 2PC
| Problema | Descrição |
|---|---|
| Bloqueio | Participantes ficam travados enquanto aguardam resposta do coordenador |
| Ponto único de falha | Se o coordenador cair, todo o sistema para |
| Baixo desempenho | Requer comunicação síncrona |
| Limitações em ambientes distribuídos | Difícil lidar com particionamento de rede |
2PC não é recomendado para microsserviços modernos
Padrão Saga
Divide uma transação longa em uma série de transações locais. Em caso de falha, faz rollback através de transações compensatórias.
flowchart LR
subgraph Normal["Fluxo Normal"]
T1["T1<br/>Criar pedido"] --> T2["T2<br/>Reservar estoque"] --> T3["T3<br/>Pagamento"] --> T4["T4<br/>Agendar entrega"]
end
flowchart LR
subgraph Failure["Em caso de falha (falha em T3)"]
T1b["T1"] --> T2b["T2"] --> T3b["T3<br/>❌ Falha"] --> C2["C2<br/>Liberar estoque"] --> C1["C1<br/>Cancelar pedido"]
end
Abordagem por Orquestração
Um orquestrador central controla a Saga.
class OrderSaga {
async execute(orderData) {
const sagaId = uuid();
let currentStep = 0;
try {
// Step 1: Criar pedido
const order = await orderService.create(orderData);
currentStep = 1;
// Step 2: Reservar estoque
await inventoryService.reserve(order.items);
currentStep = 2;
// Step 3: Pagamento
await paymentService.process(order.total);
currentStep = 3;
// Step 4: Agendar entrega
await shippingService.schedule(order);
currentStep = 4;
return { success: true, orderId: order.id };
} catch (error) {
// Executar transações compensatórias
await this.compensate(currentStep, order);
throw error;
}
}
async compensate(step, order) {
if (step >= 3) await paymentService.refund(order.id);
if (step >= 2) await inventoryService.release(order.items);
if (step >= 1) await orderService.cancel(order.id);
}
}
Abordagem por Coreografia
Cada serviço publica e assina eventos para coordenar.
flowchart TB
subgraph Success["Fluxo Normal"]
OS1["Serviço de Pedidos"] --> E1["OrderCreated"]
E1 --> IS1["Serviço de Estoque"] --> E2["InventoryReserved"]
E2 --> PS1["Serviço de Pagamento"] --> E3["PaymentProcessed"]
E3 --> SS1["Serviço de Entrega"] --> E4["ShippingScheduled"]
end
flowchart TB
subgraph Failure["Em caso de falha"]
PS2["Serviço de Pagamento"] --> EF1["PaymentFailed"]
EF1 --> IS2["Serviço de Estoque"] --> EF2["InventoryReleased<br/>(compensação)"]
EF2 --> OS2["Serviço de Pedidos"] --> EF3["OrderCancelled<br/>(compensação)"]
end
Comparação
| Aspecto | Orquestração | Coreografia |
|---|---|---|
| Visibilidade | Fluxo claro | Fluxo distribuído |
| Acoplamento | Depende do orquestrador | Baixo acoplamento entre serviços |
| Complexidade | Lógica simples | Design de eventos complexo |
| Depuração | Fácil | Difícil |
Transações Compensatórias
Em vez de fazer rollback em caso de falha, executa a operação inversa.
// Operação original
async function reserveInventory(items) {
for (const item of items) {
await db.inventory.update({
where: { productId: item.productId },
data: { quantity: { decrement: item.quantity } }
});
}
}
// Transação compensatória
async function releaseInventory(items) {
for (const item of items) {
await db.inventory.update({
where: { productId: item.productId },
data: { quantity: { increment: item.quantity } }
});
}
}
Considerações sobre Compensação
Nem todas as operações podem ser compensadas:
| Operação | Método de compensação |
|---|---|
| Envio de e-mail | Enviar “e-mail de cancelamento” |
| Início de trabalho físico | Requer intervenção manual |
| Chamadas a APIs externas | Requer API de compensação do sistema externo |
Padrão Outbox
Garante a execução confiável de atualizações de banco de dados e publicação de eventos.
// 1. Executar ambos dentro de uma transação
async function createOrder(orderData) {
await db.transaction(async (tx) => {
// Salvar dados de negócio
const order = await tx.orders.create(orderData);
// Salvar evento na tabela outbox
await tx.outbox.create({
eventType: 'OrderCreated',
payload: JSON.stringify(order),
status: 'pending'
});
});
}
// 2. Processo separado faz polling na outbox
async function publishOutboxEvents() {
const events = await db.outbox.findMany({
where: { status: 'pending' }
});
for (const event of events) {
await messageQueue.publish(event.eventType, event.payload);
await db.outbox.update({
where: { id: event.id },
data: { status: 'published' }
});
}
}
Consistência Eventual
Permite inconsistência temporária em vez de consistência imediata, garantindo que a consistência seja alcançada eventualmente.
| Modelo de Consistência | Descrição |
|---|---|
| Consistência forte | Todos veem os mesmos dados imediatamente após a escrita |
| Consistência eventual | Após a escrita, eventualmente todos verão os mesmos dados (tolerando inconsistência temporária) |
Lidando com Consistência Eventual
// Exemplo de tratamento no lado da UI
async function placeOrder(orderData) {
const result = await api.createOrder(orderData);
// Exibir como "processando" em vez de confirmado imediatamente
return {
orderId: result.orderId,
status: 'processing',
message: 'Seu pedido foi recebido. Você será notificado por e-mail após a confirmação.'
};
}
Resumo
Transações distribuídas são um desafio difícil em arquiteturas de microsserviços. 2PC não é adequado para sistemas distribuídos modernos, sendo recomendado o padrão Saga com transações compensatórias. É importante aceitar a consistência eventual e escolher o modelo de consistência adequado aos requisitos de negócio.
← Voltar para a lista