From harness-claude
Coordinates distributed transactions across microservices using orchestration and choreography sagas with compensating actions for eventual consistency when 2PC fails.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Coordinate distributed transactions using choreography and orchestration sagas with compensation.
Implements Saga Pattern for distributed transactions in Spring Boot microservices using choreography or orchestration with Kafka, RabbitMQ, or Axon Framework. Use for compensating transactions and eventual consistency across services.
Implements saga choreography for coordinating distributed workflows via event chains and compensation events without orchestration. Use for linear multi-service transactions like order fulfillment.
Implements saga orchestration patterns for coordinating multi-service transactions, handling failures with compensating actions, and managing long-running workflows using Python templates.
Share bugs, ideas, or general feedback.
Coordinate distributed transactions using choreography and orchestration sagas with compensation.
Orchestration saga (central coordinator — use for complex flows):
// The saga orchestrator is a state machine that commands each service
type SagaState =
| { step: 'pending' }
| { step: 'payment_pending'; orderId: string }
| { step: 'payment_complete'; orderId: string; chargeId: string }
| { step: 'inventory_pending'; orderId: string; chargeId: string }
| { step: 'inventory_reserved'; orderId: string; chargeId: string; reservationId: string }
| { step: 'completed'; orderId: string }
| { step: 'compensating'; failedAt: string; reason: string }
| { step: 'failed'; reason: string };
class OrderSagaOrchestrator {
constructor(
private readonly paymentService: PaymentServiceClient,
private readonly inventoryService: InventoryServiceClient,
private readonly shippingService: ShippingServiceClient,
private readonly db: SagaRepository
) {}
async execute(sagaId: string, input: OrderSagaInput): Promise<void> {
await this.db.updateState(sagaId, { step: 'payment_pending', orderId: input.orderId });
// Step 1: Process payment
let chargeId: string;
try {
const result = await this.paymentService.charge({
orderId: input.orderId,
userId: input.userId,
amount: input.amount,
});
chargeId = result.chargeId;
} catch (err) {
await this.db.updateState(sagaId, { step: 'failed', reason: 'Payment failed' });
return;
}
await this.db.updateState(sagaId, {
step: 'payment_complete',
orderId: input.orderId,
chargeId,
});
// Step 2: Reserve inventory
let reservationId: string;
try {
const result = await this.inventoryService.reserve({
orderId: input.orderId,
items: input.items,
});
reservationId = result.reservationId;
} catch (err) {
// Compensate: refund payment
await this.compensate(sagaId, chargeId, null, 'Inventory unavailable');
return;
}
await this.db.updateState(sagaId, {
step: 'inventory_reserved',
orderId: input.orderId,
chargeId,
reservationId,
});
// Step 3: Create shipment
try {
await this.shippingService.createShipment({ orderId: input.orderId, address: input.address });
} catch (err) {
// Compensate: release inventory AND refund payment
await this.compensate(sagaId, chargeId, reservationId, 'Shipping failed');
return;
}
await this.db.updateState(sagaId, { step: 'completed', orderId: input.orderId });
}
private async compensate(
sagaId: string,
chargeId: string,
reservationId: string | null,
reason: string
): Promise<void> {
await this.db.updateState(sagaId, { step: 'compensating', failedAt: 'shipping', reason });
// Compensate in reverse order
if (reservationId) {
await this.inventoryService
.releaseReservation(reservationId)
.catch((e) => console.error('Compensation failed: release inventory', e));
}
await this.paymentService
.refund(chargeId)
.catch((e) => console.error('Compensation failed: refund', e));
await this.db.updateState(sagaId, { step: 'failed', reason });
}
}
// Saga table
/*
CREATE TABLE sagas (
id UUID PRIMARY KEY,
type TEXT NOT NULL,
state JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
*/
Saga step with idempotency (compensatable operations must be idempotent):
class PaymentServiceClient {
async charge(input: ChargeInput): Promise<ChargeResult> {
const idempotencyKey = `saga:${input.orderId}:charge`;
const response = await fetch(`${this.baseUrl}/charges`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(input),
});
if (!response.ok) throw new Error(`Charge failed: HTTP ${response.status}`);
return response.json();
}
async refund(chargeId: string): Promise<void> {
const idempotencyKey = `refund:${chargeId}`;
const response = await fetch(`${this.baseUrl}/charges/${chargeId}/refund`, {
method: 'POST',
headers: { 'Idempotency-Key': idempotencyKey },
});
if (!response.ok) throw new Error(`Refund failed: HTTP ${response.status}`);
}
}
Choreography vs. Orchestration (recap):
Pivot transaction: The point of no return in a saga. Before the pivot, all steps can be compensated. After the pivot, steps must complete (they don't roll back — they may need "forward recovery").
Anti-patterns:
Saga vs. Two-Phase Commit: 2PC is synchronous and requires all participants to be available simultaneously. Saga is asynchronous and tolerant of temporary failures. 2PC guarantees strong consistency; Saga guarantees eventual consistency with compensating transactions.
Monitoring: Expose saga state via a monitoring dashboard or query endpoint. Alert on sagas stuck in compensating or pending states beyond a threshold.
microservices.io/patterns/data/saga.html