Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Event Driven Architecture Saga Patterns

ASecurity

Designing resilient, eventual-consistency distributed transactions using the Saga Pattern (Choreography and Orchestration). Includes Outbox Pattern, Change Data Capture (CDC), idempotent consumers, compensating transactions, dual-write prevention, and saga recovery mechanisms. Use when implementing distributed multi-service workflows without two-phase commit (2PC).

8 stars
0 votes
0 copies
0 views
Added 9/29/2026
ai-agentstypescriptpythongosqlreactnoderailsapidatabase

Works with

cliapi

Security Analysis

A100/100

Scanned 9/29/2026

$npx -y skills add hamzabellouch/agent-skills --skill event-driven-architecture-saga-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Event Driven Architecture Saga Patterns?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Event Driven Architecture Saga Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hamzabellouch-event-driven-architecture-saga-patterns/badge)](https://www.skillsdirectory.com/skills/hamzabellouch-event-driven-architecture-saga-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: event-driven-architecture-saga-patterns
description: Designing resilient, eventual-consistency distributed transactions using the Saga Pattern (Choreography and Orchestration). Includes Outbox Pattern, Change Data Capture (CDC), idempotent consumers, compensating transactions, dual-write prevention, and saga recovery mechanisms. Use when implementing distributed multi-service workflows without two-phase commit (2PC).
---

# Event-Driven Architecture: Saga Patterns & Eventual Consistency Guide

## 1. Architectural Foundations: BASE over ACID

In microservices architectures, each service maintains its own private database to ensure loose coupling and independent scalability. This renders distributed ACID transactions impossible without two-phase commit (2PC). However, 2PC introduces high latency, synchronous blocking, single points of failure, and deadlocks across service boundaries.

The **Saga Pattern** manages distributed transactions as a sequence of local transactions. Each local transaction updates a single service's database and emits an event or message to trigger the next step. If a step fails, the Saga executes **compensating transactions** in reverse order to undo changes and restore data consistency.

### 1.1 Transaction Classification

```
[ Local Tx 1: Compensable ] ──► [ Local Tx 2: Pivot ] ──► [ Local Tx 3: Retryable ]
        │                             │                         │
   (Rollbackable)              (Point of No Return)       (Guaranteed Success)
```

- **Compensable Transactions**: Transactions that can be undone by running an explicit compensating transaction.
- **Pivot Transaction**: The critical decision point of the Saga. Once the pivot transaction succeeds, the Saga MUST go to completion. It cannot be compensated.
- **Retryable Transactions**: Transactions that follow the pivot point and are guaranteed to eventually succeed through retries.

---

## 2. Saga Execution Models: Choreography vs. Orchestration

```
CHOREOGRAPHY:
[ Order Service ] ── OrderCreated ──► [ Payment Service ] ── PaymentProcessed ──► [ Inventory Service ]

ORCHESTRATION:
                        ┌──► [ Payment Service ]
                        │
[ Saga Orchestrator ] ──┼──► [ Inventory Service ]
                        │
                        └──► [ Shipping Service ]
```

### 2.1 Comparative Analysis

| Dimension | Choreography Saga | Orchestration Saga |
| :--- | :--- | :--- |
| **Control Model** | Decentralized; services react to domain events. | Centralized; dedicated orchestrator directs participants. |
| **Coupling** | Loosely coupled; services know events, not handlers. | Moderate coupling; orchestrator knows participant APIs. |
| **State Visibility** | Distributed across service logs; hard to query global status. | Centralized state store; easy to track saga status. |
| **Complexity** | Simple for small flows (2-3 steps); cyclic dependencies in large flows. | High initial setup; manages complex branching & parallel paths easily. |
| **Failure Handling** | Complex cascading compensation logic across events. | Explicit compensation routines directed by orchestrator. |

### 2.2 Decision Matrix
- **Choose Choreography** when workflows are simple (2-3 microservices), linear, and teams want maximum domain event decoupling.
- **Choose Orchestration** when workflows involve 4+ services, complex conditional branching, parallel execution steps, or require strict operational auditing.

---

## 3. Dual-Write Prevention: Transactional Outbox & CDC

A major vulnerability in event-driven systems is the **Dual-Write Problem**: attempting to update a database and publish a message to a broker in a single code block without atomic guarantees.

```
WRONG (Dual Write Risk):
db.orders.insert(order);       // Success
broker.publish("OrderCreated"); // Network Failure! -> DB & Broker state out of sync!
```

### 3.1 Transactional Outbox Pattern Architecture

```
[ Business Logic ]
       │
       ▼ (Single ACID Local DB Transaction)
┌──────────────────────────────────────────────┐
│  Service Database                            │
│  ├── Orders Table:   [ Insert Order ]        │
│  └── Outbox Table:   [ Insert Outbox Event ] │
└──────────────────────────────────────────────┘
       │
       ▼ (Async Log Tailing / CDC)
[ Debezium / Outbox Publisher ] ──► [ Message Broker (Kafka / RabbitMQ) ]
```

1. Business record update and message metadata insertion are committed into the service's local database within a **single local ACID transaction**.
2. A separate process (e.g., Debezium CDC or a dedicated polling worker) reads the `outbox` table and safely forwards events to the message broker with **at-least-once** delivery guarantees.

---

## 4. Idempotency & De-duplication Engineering

Because event delivery is **at-least-once**, consumers WILL receive duplicate events. Consumers must be designed to be completely **idempotent**.

### 4.1 Idempotent Consumer Design Patterns

1. **Natural Unique Key Constraints**: Insert incoming event processing results using database unique constraints (e.g., `PRIMARY KEY (payment_id)`).
2. **Idempotency Key / Deduplication Table**:
   ```sql
   CREATE TABLE processed_events (
       event_id VARCHAR(128) PRIMARY KEY,
       consumer_group VARCHAR(64) NOT NULL,
       processed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
   );
   ```
   Before processing logic begins, attempt atomic insertion into `processed_events`. If duplicate key violation occurs, skip processing and immediately ACK message.
3. **State Machine Guardrails**: Only allow state transitions that are valid from the current state (e.g., `UPDATE orders SET status = 'PAID' WHERE id = :id AND status = 'PENDING'`).

---

## 5. Compensating Transactions & Failure Recovery

Compensating transactions are inverse operations that semantically undo completed local transactions.

### 5.1 Rules for Compensating Actions
- **Must Be Idempotent**: A compensating step may be retried multiple times during recovery.
- **Must Never Fail Permanently**: If a compensating action encounters transient errors, it must retry until successful. If business invariants prevent auto-compensation, route to a **Human-in-the-Loop (HITL)** DLQ queue.
- **Semantic Rollback**: Compensation does not necessarily mean "deleting" rows. It often means applying a counter-action (e.g., issuing a credit refund rather than un-writing a credit charge).

---

## 6. Critical Anti-Patterns & Production Traps

1. **Dual Writes without Outbox Pattern**: Updating local DB state and calling `broker.publish()` directly without outbox tables or transactional CDC.
2. **Missing Compensating Triggers**: Implementing forward execution steps without defining corresponding inverse compensation handlers.
3. **Cyclic Choreography Dependencies**: Service A triggers Service B, which triggers Service C, which triggers Service A again, creating un-traceable infinite loops.
4. **Non-Idempotent Compensations**: Compensating transactions that double-credit or double-refund when retried.
5. **Synchronous REST Calls inside Sagas**: Mixing blocking HTTP calls within asynchronous event-driven sagas, leading to thread pool exhaustion and cascading failures.

---

## 7. Production Code Reference

### 7.1 Transactional Outbox Pattern in Python (SQLAlchemy)

```python
import uuid
import json
from datetime import datetime, timezone
from sqlalchemy import create_engine, Column, String, Float, DateTime, Text
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

class Order(Base):
    __tablename__ = 'orders'
    id = Column(String(64), primary_key=True)
    customer_id = Column(String(64), nullable=False)
    total_amount = Column(Float, nullable=False)
    status = Column(String(32), nullable=False)

class OutboxEvent(Base):
    __tablename__ = 'outbox'
    id = Column(String(64), primary_key=True)
    aggregate_type = Column(String(64), nullable=False)
    aggregate_id = Column(String(64), nullable=False)
    event_type = Column(String(64), nullable=False)
    payload = Column(Text, nullable=False)
    created_at = Column(DateTime(timezone=True), nullable=False)

engine = create_engine("postgresql://app_user:secret@db.prod.internal:5432/orders_db")
SessionLocal = sessionmaker(bind=engine)

def create_order_with_outbox(customer_id: str, amount: float) -> str:
    session = SessionLocal()
    order_id = f"ord-{uuid.uuid4()}"
    event_id = f"evt-{uuid.uuid4()}"
    
    order = Order(
        id=order_id,
        customer_id=customer_id,
        total_amount=amount,
        status="PENDING_PAYMENT"
    )
    
    event_payload = json.dumps({
        "event_id": event_id,
        "order_id": order_id,
        "customer_id": customer_id,
        "amount": amount,
        "timestamp": datetime.now(timezone.utc).isoformat()
    })
    
    outbox_entry = OutboxEvent(
        id=event_id,
        aggregate_type="Order",
        aggregate_id=order_id,
        event_type="OrderCreated",
        payload=event_payload,
        created_at=datetime.now(timezone.utc)
    )

    try:
        # Atomic single local ACID transaction
        session.add(order)
        session.add(outbox_entry)
        session.commit()
        return order_id
    except Exception as e:
        session.rollback()
        raise RuntimeError(f"Failed to create order atomically: {str(e)}")
    finally:
        session.close()
```

### 7.2 Saga Orchestrator State Machine in TypeScript / Node.js

```typescript
type SagaState = 'STARTED' | 'PAYMENT_RESERVED' | 'INVENTORY_RESERVED' | 'COMPLETED' | 'FAILED' | 'COMPENSATING';

interface SagaContext {
  sagaId: string;
  orderId: string;
  customerId: string;
  amount: number;
  items: Array<{ sku: string; qty: number }>;
  currentState: SagaState;
  failureReason?: string;
}

export class OrderSagaOrchestrator {
  private context: SagaContext;

  constructor(context: SagaContext) {
    this.context = context;
  }

  async executeSaga(): Promise<void> {
    try {
      // Step 1: Reserve Payment (Compensable)
      await this.executeStep('PAYMENT_RESERVED', 
        () => this.callPaymentService('/payments/reserve', { amount: this.context.amount }),
        () => this.callPaymentService('/payments/refund', { amount: this.context.amount })
      );

      // Step 2: Reserve Inventory (Compensable)
      await this.executeStep('INVENTORY_RESERVED', 
        () => this.callInventoryService('/inventory/reserve', { items: this.context.items }),
        () => this.callInventoryService('/inventory/release', { items: this.context.items })
      );

      // Step 3: Pivot Point - Approve Order (Retryable)
      this.context.currentState = 'COMPLETED';
      await this.updateSagaState('COMPLETED');
      await this.callOrderService(`/orders/${this.context.orderId}/approve`, {});

    } catch (error: any) {
      console.error(`Saga ${this.context.sagaId} failed at state ${this.context.currentState}: ${error.message}`);
      this.context.failureReason = error.message;
      await this.compensateSaga();
    }
  }

  private async executeStep(
    targetState: SagaState, 
    action: () => Promise<void>, 
    compensation: () => Promise<void>
  ): Promise<void> {
    try {
      await action();
      this.context.currentState = targetState;
      await this.updateSagaState(targetState);
    } catch (err) {
      // Register compensation step for reverse execution
      this.compensations.push(compensation);
      throw err;
    }
  }

  private compensations: Array<() => Promise<void>> = [];

  private async compensateSaga(): Promise<void> {
    this.context.currentState = 'COMPENSATING';
    await this.updateSagaState('COMPENSATING');

    // Run compensations in reverse order
    for (const compensation of this.compensations.reverse()) {
      let retryCount = 0;
      let success = false;
      
      while (!success && retryCount < 5) {
        try {
          await compensation();
          success = true;
        } catch (compErr) {
          retryCount++;
          console.error(`Compensation retry ${retryCount} failed:`, compErr);
          await new Promise(res => setTimeout(res, 1000 * Math.pow(2, retryCount)));
        }
      }

      if (!success) {
        // Escalate to Human-In-The-Loop / DLQ Alerting
        console.error(`CRITICAL: Compensation failed permanently for Saga ${this.context.sagaId}`);
        await this.routeToHumanInterventionDLQ(this.context);
      }
    }

    this.context.currentState = 'FAILED';
    await this.updateSagaState('FAILED');
  }

  private async updateSagaState(state: SagaState): Promise<void> {/* DB state update */}
  private async callPaymentService(path: string, payload: any): Promise<void> {/* HTTP/gRPC */}
  private async callInventoryService(path: string, payload: any): Promise<void> {/* HTTP/gRPC */}
  private async callOrderService(path: string, payload: any): Promise<void> {/* HTTP/gRPC */}
  private async routeToHumanInterventionDLQ(context: SagaContext): Promise<void> {/* DLQ Alert */}
}
```

Attribution

hamzabellouchhamzabellouch
View sourceSee grades on GitHubMore from hamzabellouch →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698621 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →