git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/microservices-saga-pattern" ~/.claude/skills/intense-visions-harness-engineering-microservices-saga-pattern && rm -rf "$T"
agents/skills/claude-code/microservices-saga-pattern/SKILL.mdMicroservices: Saga Pattern
Coordinate distributed transactions using choreography and orchestration sagas with compensation.
When to Use
- A business transaction spans multiple services and you can't use a distributed 2-phase commit
- You need eventual consistency with rollback capability when any step fails
- You're processing orders, bookings, financial transfers, or any multi-step workflow
- You need visibility into the overall progress of a multi-step distributed operation
Instructions
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}`); } }
Details
Choreography vs. Orchestration (recap):
- Choreography: Services react to events. No central coordinator. Good for simple flows.
- Orchestration: A saga object commands each service. Central visibility. Better for complex flows with branching.
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:
- Compensation that can also fail — use retries with backoff; log and alert on persistent compensation failures
- Not persisting saga state — if the orchestrator crashes mid-saga, you have no way to resume
- Synchronous saga steps that all use 2PC under the hood — defeats the purpose; use async or accept eventual consistency
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.
Source
microservices.io/patterns/data/saga.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.