Skip to content

About

Saga Pattern Architecture

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

17 Commits

Folders and files

Repository files navigation

πŸ”„ Saga Pattern Architecture β€” Distributed Transaction Management

CI License

Production-grade Saga Orchestration Pattern for managing distributed transactions across microservices with full compensation logic, idempotency, and observability.

Architecture

πŸ“ Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        API GATEWAY (Kong)                           β”‚
β”‚              Rate Limiting Β· Auth Β· Request Routing                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    SAGA ORCHESTRATOR SERVICE                        β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Saga State  β”‚  β”‚  Step Runner β”‚  β”‚  Compensation Manager    β”‚  β”‚
β”‚  β”‚   Machine    β”‚  β”‚  (Async)     β”‚  β”‚  (Rollback Logic)        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                     β”‚
β”‚  State Store: Redis (distributed locks) or in-memory (PoC)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚              β”‚              β”‚              β”‚
         β–Ό              β–Ό              β–Ό              β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Order   β”‚  β”‚   Payment    β”‚  β”‚Inventory β”‚  β”‚ Notification   β”‚
  β”‚ Service  β”‚  β”‚   Service    β”‚  β”‚ Service  β”‚  β”‚   Service      β”‚
  β”‚          β”‚  β”‚              β”‚  β”‚          β”‚  β”‚                β”‚
  β”‚ Postgres β”‚  β”‚  Postgres    β”‚  β”‚ Postgres β”‚  β”‚  Email/SMS     β”‚
  β”‚ (Orders) β”‚  β”‚  (Ledger)    β”‚  β”‚ (Stock)  β”‚  β”‚                β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚              β”‚              β”‚              β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚    Apache Kafka     β”‚
                    β”‚                    β”‚
                    β”‚  Topics:           β”‚
                    β”‚  β€’ saga-*-commands β”‚
                    β”‚  β€’ saga-replies    β”‚
                    β”‚  β€’ saga-events     β”‚
                    β”‚  β€’ *.dlq           β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Note: Kong is shown as an optional API gateway. It is not included in docker-compose.yml.


🧩 Saga Flow β€” Order Processing

Client ──► Orchestrator: CREATE_ORDER_SAGA
              β”‚
              β”œβ”€Step 1──► OrderService: RESERVE_ORDER
              β”‚           ◄── SUCCESS: order_id=123, status=RESERVED
              β”‚
              β”œβ”€Step 2──► PaymentService: PROCESS_PAYMENT
              β”‚           ◄── SUCCESS: payment_id=456, amount=99.99
              β”‚
              β”œβ”€Step 3──► InventoryService: RESERVE_INVENTORY
              β”‚           ◄── FAILURE: insufficient_stock
              β”‚
              β”‚  ⚠ COMPENSATION TRIGGERED (reverse order)
              β”‚
              β”œβ”€Comp 2──► PaymentService: REFUND_PAYMENT (payment_id=456)
              β”‚           ◄── SUCCESS: refund_id=789
              β”‚
              β”œβ”€Comp 1──► OrderService: CANCEL_ORDER (order_id=123)
              β”‚           ◄── SUCCESS: status=CANCELLED
              β”‚
              └──► Client: ORDER_FAILED (saga_id=xxx, reason=OUT_OF_STOCK)

πŸ—οΈ Key Design Decisions

1. Orchestration vs Choreography

We chose orchestration β€” see ADR-001 for full reasoning.

2. Idempotency

Every saga step is idempotent via idempotency keys β€” see ADR-004.

3. Saga State Machine

STARTED β†’ RUNNING β†’ COMPLETED
        β†’ RUNNING β†’ COMPENSATING β†’ FAILED
                  β†’ COMPENSATION_FAILED  (needs manual intervention β€” see runbook)

πŸ“ Project Structure

β”œβ”€β”€ saga-orchestrator/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ SagaOrchestrator.ts          # Core orchestration engine + saga registry
β”‚   β”‚   β”œβ”€β”€ SagaStateMachine.ts          # State transitions + validation
β”‚   β”‚   β”œβ”€β”€ CompensationManager.ts       # Reverse-order rollback logic
β”‚   β”‚   β”œβ”€β”€ StepExecutor.ts              # Async step dispatch with retry + timeout
β”‚   β”‚   β”œβ”€β”€ SagaRepository.ts            # All DB access for saga state
β”‚   β”‚   β”œβ”€β”€ saga-bootstrap.service.ts    # Registers saga definitions + Kafka consumers
β”‚   β”‚   β”œβ”€β”€ db-init.service.ts           # Idempotent schema bootstrap on startup
β”‚   β”‚   β”œβ”€β”€ app.module.ts                # NestJS module wiring
β”‚   β”‚   β”œβ”€β”€ main.ts                      # NestJS bootstrap (Fastify + shutdown hooks)
β”‚   β”‚   β”œβ”€β”€ sagas/
β”‚   β”‚   β”‚   └── order.saga.ts            # OrderSaga step definitions
β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   β”œβ”€β”€ orders.controller.ts     # POST /api/orders β€” start order saga
β”‚   β”‚   β”‚   β”œβ”€β”€ sagas.controller.ts      # GET /api/sagas/:id β€” saga status
β”‚   β”‚   β”‚   β”œβ”€β”€ metrics.controller.ts    # GET /metrics β€” Prometheus scrape endpoint
β”‚   β”‚   β”‚   └── probes.controller.ts     # GET /live  GET /ready (K8s probes)
β”‚   β”‚   β”œβ”€β”€ metrics/
β”‚   β”‚   β”‚   └── metrics.service.ts       # Prometheus counters (prom-client)
β”‚   β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”‚   └── saga.entity.ts           # TypeORM entity
β”‚   β”‚   β”œβ”€β”€ kafka/
β”‚   β”‚   β”‚   └── kafka.service.ts         # Kafka producer + consumer (DLQ + retry)
β”‚   β”‚   └── redis/
β”‚   β”‚       └── redis.service.ts         # Distributed locks + in-memory fallback
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   └── package.json
β”‚
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ order-service/src/index.ts       # RESERVE_ORDER + CANCEL_ORDER handlers
β”‚   β”œβ”€β”€ payment-service/src/index.ts     # PROCESS_PAYMENT + REFUND_PAYMENT handlers
β”‚   β”œβ”€β”€ inventory-service/src/index.ts   # RESERVE_INVENTORY + RELEASE_INVENTORY handlers
β”‚   └── notification-service/src/index.ts  # SAGA_COMPLETED / SAGA_FAILED email+SMS
β”‚
β”œβ”€β”€ deploy/
β”‚   β”œβ”€β”€ helm/saga-pattern/               # Helm chart (Deployment, Service, HPA, PDB, Ingress)
β”‚   β”œβ”€β”€ kustomize/                       # Kustomize base + kind overlay
β”‚   β”œβ”€β”€ kind/                            # One-command local K8s cluster scripts
β”‚   β”‚   β”œβ”€β”€ create-cluster.sh
β”‚   β”‚   β”œβ”€β”€ install-bitnami-infra.sh     # Kafka + Redis + Postgres via Bitnami Helm
β”‚   β”‚   β”œβ”€β”€ build-images.sh
β”‚   β”‚   β”œβ”€β”€ deploy-app-helm.sh
β”‚   β”‚   └── port-forward-orchestrator.sh
β”‚   └── README.md
β”‚
β”œβ”€β”€ docker/
β”‚   β”œβ”€β”€ docker-compose.yml
β”‚   └── prometheus.yml
β”‚
└── docs/
    β”œβ”€β”€ saga-flow.svg
    β”œβ”€β”€ adr/
    β”‚   β”œβ”€β”€ 001-orchestration-vs-choreography.md
    β”‚   β”œβ”€β”€ 002-redis-for-saga-state.md
    β”‚   β”œβ”€β”€ 003-kafka-for-commands.md
    β”‚   └── 004-idempotency-strategy.md
    └── runbooks/
        └── compensation-failed.md

πŸš€ Running Locally

Option A β€” Docker Compose (quickest)

# Start everything: Kafka, Redis, Postgres x4, Jaeger, Prometheus, Grafana
docker compose -f docker/docker-compose.yml up -d --build

# Trigger a test saga
curl -X POST http://localhost:3000/api/orders \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust-123",
    "items": [{"productId": "a0000000-0000-0000-0000-000000000001", "qty": 2, "unitPrice": 49.99}],
    "paymentMethod": "card_xxxx_4242"
  }'

# Monitor saga state
curl http://localhost:3000/api/sagas/{saga_id}

# View Kafka messages
open http://localhost:8090   # Kafka UI

# View distributed traces
open http://localhost:16686  # Jaeger

# View metrics dashboard
open http://localhost:3001   # Grafana (admin / admin)

Option B β€” In-memory state store (no Redis required)

SAGA_STATE_STORE=memory docker compose -f docker/docker-compose.yml up -d --build

Option C β€” Kubernetes via kind + Helm

Prereqs: docker, kubectl, helm, kind

cd deploy/kind

./create-cluster.sh               # Create local K8s cluster
./install-bitnami-infra.sh        # Install Kafka, Redis, Postgres via Bitnami Helm
./build-images.sh                 # Build Docker images
./deploy-app-helm.sh              # Deploy app via Helm
./port-forward-orchestrator.sh    # Expose orchestrator on localhost:8080

# Health checks
curl http://localhost:8080/live
curl http://localhost:8080/ready

See deploy/README.md for Kustomize instructions and production notes.


πŸ” Observability

Tool Purpose Port
Jaeger Distributed tracing across saga steps :16686
Prometheus Metrics scraping (GET /metrics) :9090
Grafana Metrics dashboard :3001
Kafka UI Message inspection and replay :8090

Prometheus Metrics

Metric Type Description
saga_started_total Counter Sagas started, labeled by sagaName
saga_completed_total Counter Sagas completed successfully, labeled by sagaName
saga_failed_total Counter Sagas failed after compensation, labeled by sagaName
Node.js defaults Various CPU, memory, GC, event loop (via prom-client)

πŸ›‘οΈ Resilience Patterns

  • Distributed Locking β€” Redis SET NX PX prevents duplicate step execution across pods
  • Idempotency Keys β€” each step and compensation is safe to retry
  • Retry with Exponential Backoff β€” Kafka consumer retries with jitter before routing to DLQ
  • Dead Letter Queue β€” unprocessable messages sent to {topic}.dlq after max retries
  • Step Timeout β€” Redis TTL-based timeout per step
  • COMPENSATION_FAILED state β€” on-call alerting for stuck compensations (see runbook)
  • Graceful shutdown β€” enableShutdownHooks() for clean Kubernetes pod termination
  • Readiness probe β€” /ready checks Postgres, Kafka, and Redis before accepting traffic

πŸ§ͺ Testing

cd saga-orchestrator

npm run test:unit          # State machine logic, compensation ordering
npm run test:integration   # With real Kafka + Postgres
npm run test:chaos         # Kill services mid-saga
npm run test:load          # Concurrent sagas (requires Artillery)

πŸ“š Architecture Decision Records

  • ADR-001 β€” Why orchestration over choreography
  • ADR-002 β€” Redis as distributed lock store
  • ADR-003 β€” Kafka command channels
  • ADR-004 β€” Idempotency key design

About

Saga Pattern Architecture

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages