惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
Last Week in AI
Last Week in AI
G
Google Developers Blog
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
IT之家
IT之家
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
M
Microsoft Research Blog - Microsoft Research
Blog — PlanetScale
Blog — PlanetScale
D
Docker
F
Fortinet All Blogs
A
About on SuperTechFans
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
Cyber Attacks, Cyber Crime and Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
小众软件
小众软件
PCI Perspectives
PCI Perspectives
GbyAI
GbyAI
Recorded Future
Recorded Future
E
Exploit-DB.com RSS Feed
V
V2EX - 技术
S
Schneier on Security
S
Security Archives - TechRepublic
I
InfoQ
Hacker News - Newest:
Hacker News - Newest: "LLM"
L
LINUX DO - 最新话题
W
WeLiveSecurity
Security Latest
Security Latest
博客园 - 三生石上(FineUI控件)
T
The Blog of Author Tim Ferriss
Stack Overflow Blog
Stack Overflow Blog
Stack Overflow Blog
Stack Overflow Blog
Hugging Face - Blog
Hugging Face - Blog
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
Secure Thoughts
B
Blog RSS Feed
N
Netflix TechBlog - Medium
C
Comments on: Blog
SecWiki News
SecWiki News
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
雷峰网
雷峰网
P
Proofpoint News Feed
I
Intezer

DEV Community

Zero Heap Allocations at 1.18 GB/s: Deep Dive into ForgeZero 4.0.x The Minimum Viable Test Suite for Working with Agents Why Perplexity Started Citing My Blog: 5 Changes That Actually Worked Sync Supabase via OAuth: No Connection String Needed I asked three AI models the same API question. Only one had it right. Why does AI forget what you said (and how to fix it) I built a daily Wordle-style game for AI tools - Here's how Mapping Polish company structures: querying KRS direct via API Built tmpdrop — a tiny self-hosted ephemeral file drop Running Local LLM - 0$ Personal Agentic AI Assistant - Part 3 LLD Object-Oriented Design: Interfaces & Abstract Classes (Designing Contracts) The Smaller Ship: Vitalik, the Ethereum Foundation's Restructuring, and What It Leaves for Investors Looking for 4 people to build something weird with me Building a Local-Only RAG System with Ollama and TypeScript The False Positive Tax: a 1:1 TP:FP analysis of eslint-plugin-security What's new in Data Preprocessor 1.5.x — R codegen, Robust Scaler, and a deadlock post-mortem How I self-hosted my Flask app on an old laptop for almost free I built a free DSA interview prep site because I was tired of the existing options I built an AI agent that migrates Next.js Pages Router to App Router Prisma Query Logging and PostgreSQL: Where the ORM Ends and the Database Begins Prisma query logging y PostgreSQL: dónde termina el ORM y empieza la base From Browser to Server : The Journey of an HTTP Request (Demystifying the Web’s Infrastructure) Santa Augmentcode Intent Ep.6 I Benchmarked 17 ESLint Security Plugins. Only One Found Every Vulnerability. How to Build a High-Performance Image Optimization Pipeline in 5 Minutes 50 Linux Commands Every DevOps Engineer Must Know Less Toil, More Flow - Automating the Path from Request to Implementation The Code Review Checklist I Actually Use How I run a small blog on Astro 5 + Content Collections Git: Best Practices for Professionals How IBM Bob Became My Everyday Coding Companion Solana Passkey Wallet: Replacing Seed Phrases with SIMD-0075 I built a small browser puzzle game about arrows I wrapped Claude Code in a zsh function. Here's every decision I almost got wrong. Mobile Game Optimization: A Unity Developer's Checklist Git: Best Practices for Beginners Three days I lost chasing a ghost that was already dead on disk Why Too Many Parts Hurt ClickHouse Performance Guardrails for Agent Output: Pluggable Validation Before and After LLM Calls Gemma Forge: Local AI Without the Setup Wall From Half‑dead Prototype to Local‑Only AI Medical Assistant: Rewiring MedClinic with GitHub Copilot Runninig a forkbomb in Jenkins What’s Actually Happening When You Use Git Preventing Recursive Tool Loops in LangChain Agents Building a Rock-Paper-Scissors CLI with TypeScript — Union Types, Conditionals, and Jest Your AI Coding Agent Wastes 80% of Its Context. Fixed That with Graph Theory. Why Flutter Has Become the Go-To Framework for Fintech App Development We built a scripting language just for AI agents. Here's why. Stop building AI inboxes. Build decision layers instead. Meme Monday Why I Built @editora/ui-react? Are AI tools the next level of abstraction in software development? Identity on Solana: Your Wallet Is Your Account One API Call Changed Everything The Internet Career Nobody Talks About Enough: What Is DevRel? Solar Panel Wiring Diagram: Series vs Parallel Hello everyone! Glad to join the dev.to community I Built an AI Agent That Tailors My Resume - Here's How Agents Actually Work I Built a WhatsApp OTP + AI Chatbot Platform for African Businesses MTP Explained — And Why It Matters for Android on Mac Most Beginners Learn Full-Stack Development Backwards GitHub Glow-Up: Open Source, READMEs, Badges, Streaks, Git and gh CLI System Design Cheat Sheet: Concepts Every Developer Should Know Are Junior Developer Roles Actually Dying? A Fresher's Honest Take Using DigitalOcean Droplets as Ephemeral Sandboxes for AI Agents I built a VSCode extension that visualises your code navigation as a call tree — made for legacy codebase pain Vite predev/prebuild: chaining scripts without losing your mind A website to save you from messy browser tabs Dear Web2 Developer... Solana is here calling Postgres JSONB indexes: GIN vs BTREE on the same column The $5 AI That Remembers Everything What are your goals for the week? #180 Zettelkasten for Developers: A Practical Method That Works OpenClaw vs Hermes Agent: Stars, Downloads & Usage 2026 `act` vs. `waitFor` Global Teams Don’t Struggle With Time Zones. They Struggle With Context Python as a JavaScript Dev $5.4 Billion in Damage. 8.5 Million Machines Down. Three YAML Controls Would Have Prevented It. Here's the Structural Analysis. 🚫 Stop Using PN532 V1 for Your NFC Projects (Real Debugging Experience) Probabilistic Graph Neural Inference for smart agriculture microgrid orchestration for extreme data sparsity scenarios Inference Is Becoming the New Steady-State Cost Center Why AI-Generated Code Is Always Good Enough — And Never Great I built a dark admin dashboard template in HTML — no React, no npm, just pure HTML What is the Difference Between Lattice-Based and Hash-Based Signatures? Next.js App Router caching: revalidate, dynamic, and no-store without the folklore Next.js App Router caching: revalidate, dynamic y no-store sin folklore I built Stashly — a full-stack content manager with a rich text editor published: false tags: react, node, mongodb, typescript Why I Started Building React Projects Instead of Just Watching Tutorials ? Every Tool Eventually Becomes Tuesday Nobody Warns You That Real Software Engineering Feels Chaotic Tích hợp VNPay, Stripe trong Odoo 19 BeautifulSoup and Requests for Web Scraping With Python: When Simple Still Works I Was Stuck Debugging React — Then Developer Tools Changed It Buck Converter Ripple: Sizing the Inductor and Capacitor With Confidence AWS Just Made Its MCP Server Generally Available. Here's What It Actually Gives AI Agents. RAMPART Tests Your AI Agents in Dev. What Catches Malicious Tool Calls in Production? Vibe Team Software Engineering: What a Real AI Human Dev Team Workflow Actually Looks Like An npm Package for AI Agent Orchestration Just Shipped With Its Front Door Unlocked. Here's What the CVE Actually Reveals. Microsoft Foundry Just Added CI/CD for AI Agents. Here's What That Actually Changes. The Best Career Insurance Is a Tech Event You Don't Want to Attend
Implementing Saga Pattern With Lambda Durable Function
Rishi · 2026-05-25 · via DEV Community

When you hit the “Place Order” button, that event triggers a series of steps, including inventory reservation, payment processing, and shipping initialization. Now, suppose your card is charged by the payment service (Stripe 🤔), but the API call to the third-party shipping service failed.

Modern systems don’t live inside a single database anymore. You can’t just rollback everything like a normal database transaction. In this era of distributed services, Saga patterns solve the problem of distributed rollback.

What is the Saga Pattern?

Saga is a sequence of steps carried out in a workflow. For each successful step, there exists a compensating step. As the saga progresses, these steps are stored in a list. Down the lane, if any step fails, all compensating steps are executed in reverse order.

Saga Pattern with AWS Durable Lambda Functions

Because of built-in checkpoints and replay mechanisms, AWS Lambda functions are perfect for implementing the saga pattern.

Each step of the saga can be wrapped in a durable step, allowing independent retry strategies. After each successful step, durable function checkpoints state; hence, on retry, already completed steps are not executed again.

To implement forward steps and compensation, all that we need is a try-catch block. The idea is simple: keep moving forward in the try block. If any step fails, compensating steps run in the catch block.

import {
  withDurableExecution,
  DurableContext,
  createRetryStrategy,
  JitterStrategy,
} from '@aws/durable-execution-sdk-js';
import { randomUUID } from 'crypto';

//Types

interface OrderEvent {
  orderId: string;
  customerId: string;
  items: Array<{ sku: string; qty: number; price: number }>;
  paymentMethod: { type: string; token: string };
  shippingAddress: { street: string; city: string; zip: string };
}

// Custom Error Classes
// Used by retryableErrorTypes - SDK checks instanceof, so real classes are needed

class NetworkError extends Error {
  name = 'NetworkError';
}

class PaymentDeclinedError extends Error {
  name = 'PaymentDeclinedError';
}

// Retry Strategies

/**
 * Exponential backoff with jitter - for external API calls.
 * Attempts: 1s → 2s → 4s → 8s → 16s (random jitter added to avoid thundering herd)
 * Only retries NetworkError - other errors won't be retried.
 */
const apiRetryStrategy = createRetryStrategy({
  maxAttempts: 5,
  initialDelay: { seconds: 1 },
  maxDelay: { seconds: 30 },
  backoffRate: 2.0,
  jitter: JitterStrategy.FULL,
  retryableErrorTypes: [NetworkError], // ← class constructor, not string
});

/**
 * Custom retry for payment - retries network errors but NOT declined cards.
 * Uses a function instead of createRetryStrategy for fine-grained control.
 */
const paymentRetryStrategy = (error: Error, attemptCount: number) => {
  // retryableErrorTypes equivalent - never retry a declined card
  if (error instanceof PaymentDeclinedError) {
    return { shouldRetry: false };
  }

  // maxAttempts: 5 equivalent
  if (attemptCount >= 5) {
    return { shouldRetry: false };
  }

  // initialDelay(1s) + backoffRate(2.0) + maxDelay(30s) + jitter(FULL) equivalent
  const baseDelay = 1 * Math.pow(2.0, attemptCount - 1); // exponential: 1s → 2s → 4s → 8s → 16s
  const capped = Math.min(baseDelay, 30);                 // maxDelay: never exceed 30s
  const jittered = Math.random() * capped;                // FULL jitter: random between 0 and capped
  const seconds = Math.max(1, Math.round(jittered));      // minimum 1s, rounded to whole second

  return { shouldRetry: true, delay: { seconds } };
};

// Handler

export const handler = withDurableExecution(async (event: OrderEvent, context: DurableContext) => {
  context.logger.info('Order processing started', { orderId: event.orderId });

  // Saga compensations array tracks what to undo if something fails later
  const compensations: Array<{ name: string; fn: () => Promise<void> }> = [];

  try {

    // ── Step 1: Validate ────────────────────────────────────────────────────
    // Throws a plain Error for invalid input, "shouldRetry: false" strategy means it fails immediately without retry
    await context.step('validate-order', async () => {
      context.logger.info('Validating order');

      if (!event.orderId || !event.customerId) {
        throw new Error('Order missing required fields');
      }
      if (!event.items || event.items.length === 0) {
        throw new Error('Order has no items');
      }

      const total = event.items.reduce((sum, i) => sum + i.price * i.qty, 0);
      if (total <= 0) {
        throw new Error('Order total must be greater than zero');
      }

      return { valid: true, total };
    },
      { retryStrategy: () => ({ shouldRetry: false }) }
    );

    // ── Step 2: Reserve inventory ───────────────────────────────────────────
    // Retries with exponential backoff, inventory service may be temporarily busy
    const reservation = await context.step(
      'reserve-inventory',
      async () => {
        context.logger.info('Reserving inventory');
        return await callInventoryService(event.orderId, event.items);
      },
      { retryStrategy: apiRetryStrategy }
    );

    // Register compensation, if something fails later, cancel this reservation
    compensations.push({
      name: 'cancel-reservation',
      fn: async () => { await callInventoryService(event.orderId, event.items, 'cancel'); },
    });

    context.logger.info('Inventory reserved', { reservationId: reservation.id });

    // ── Step 3: Generate idempotency key ───────────────────────────────────
    // Key is generated ONCE inside a step then checkpointed and same value returned on every replay.
    // This is the recommended pattern for payment APIs that support idempotency keys.
    // WARNING: Never generate outside a step because it changes on replay, defeating deduplication.
    const idempotencyKey = await context.step('payment-idempotency-key', async () =>
      randomUUID()
    );

    // ── Step 4: Charge payment ──────────────────────────────────────────────
    // AtLeastOnce (default) + idempotency key = safe to retry.
    // Even if Lambda crashes after charge but before checkpoint, the retry sends
    // the same idempotency key - payment provider deduplicates and returns original result.
    // No double charge risk.
    const payment = await context.step(
      'charge-payment',
      async () => {
        context.logger.info('Charging payment', { idempotencyKey });
        return await callPaymentService(event.paymentMethod, getTotalAmount(event), idempotencyKey);
      },
      { retryStrategy: paymentRetryStrategy } // retries NetworkError safely
    );

    // Register compensation - if shipping fails, refund the payment
    compensations.push({
      name: 'refund-payment',
      fn: async () => {
        await callPaymentService(event.paymentMethod, getTotalAmount(event), idempotencyKey, 'refund', payment.id);
      },
    });

    context.logger.info('Payment charged', { paymentId: payment.id });

    // ── Step 4: Create shipment ─────────────────────────────────────────────
    // Retries with exponential backoff, shipping service may be temporarily down
    const shipment = await context.step(
      'create-shipment',
      async () => {
        context.logger.info('Creating shipment');
        return await callShippingService(event.orderId, event.shippingAddress, event.items);
      },
      { retryStrategy: apiRetryStrategy }
    );

    context.logger.info('Shipment created', { trackingId: shipment.trackingId });

    // ── Step 5: Send confirmation ───────────────────────────────────────────
    // Simple fixed-delay retry: email service is reliable, 3 attempts is enough
    await context.step(
      'send-confirmation',
      async () => {
        context.logger.info('Sending confirmation email');
        await callNotificationService(event.customerId, event.orderId, shipment.trackingId);
      },
      {
        retryStrategy: createRetryStrategy({
          maxAttempts: 3,
          initialDelay: { seconds: 2 },
          backoffRate: 1, // fixed delay, no backoff
        }),
      }
    );

    context.logger.info('Order processing complete', { orderId: event.orderId });

    return {
      success: true,
      orderId: event.orderId,
      reservationId: reservation.id,
      paymentId: payment.id,
      trackingId: shipment.trackingId,
    };

  } catch (error) {
    // ── Saga: undo completed steps in reverse order ─────────────────────────
    // Example: payment charged but shipping failed → refund payment, cancel reservation
    context.logger.error('Order failed, running compensations', {
      orderId: event.orderId,
      error: (error as Error).message,
    });

    for (const comp of compensations.reverse()) {
      try {
        // Each compensation is its own durable step, checkpointed and retried
        await context.step(comp.name, async () => comp.fn());
        context.logger.info(`Compensation done: ${comp.name}`);
      } catch (compError) {
        // Log but continue, try all compensations even if one fails
        context.logger.error(`Compensation failed: ${comp.name}`, compError);
      }
    }

    throw error; // re-throw so execution is marked FAILED in console
  }
});

// ─── Simulated Service Calls ──────────────────────────────────────────────────
// In real code these would call actual APIs. Here we simulate occasional failures
// to demonstrate retry behavior.

async function callInventoryService(
  orderId: string,
  items: OrderEvent['items'],
  action = 'reserve'
): Promise<{ id: string }> {
  // 20% chance of network failure - demonstrates retry kicking in
  if (Math.random() < (3 / 5)) throw new NetworkError('Inventory service timeout');
  return { id: `RES-${orderId}-${Date.now()}` };
}

async function callPaymentService(
  method: OrderEvent['paymentMethod'],
  amount: number,
  idempotencyKey: string,
  action = 'charge',
  paymentId?: string
): Promise<{ id: string }> {
  if (method.token === 'DECLINED') throw new PaymentDeclinedError('Card declined');
  if (Math.random() < (2 / 3)) throw new NetworkError('Payment gateway timeout');
  return { id: `PAY-${idempotencyKey}` };
}

async function callShippingService(
  orderId: string,
  address: OrderEvent['shippingAddress'],
  items: OrderEvent['items']
): Promise<{ trackingId: string }> {
  if (Math.random() < (2 / 3)) throw new NetworkError('Shipping service unavailable');
  return { trackingId: `TRACK-${orderId}-${Date.now()}` };
}

async function callNotificationService(
  customerId: string,
  orderId: string,
  trackingId: string
): Promise<void> {
  console.log(`Confirmation sent to ${customerId} for order ${orderId}, tracking: ${trackingId}`);
}

function getTotalAmount(event: OrderEvent): number {
  return event.items.reduce((sum, i) => sum + i.price * i.qty, 0);
}

Enter fullscreen mode Exit fullscreen mode

Let’s run the code!

Copy and paste the above code into the durable Lambda code editor. Then use the commands below to invoke the lambda with different payloads

Success scenario:-

aws lambda invoke \
  --function-name durable-parallel-test:prod \
  --payload '{"orderId":"ORD-001","customerId":"CUST-123","items":[{"sku":"ITEM-1","qty":2,"price":25}],"paymentMethod":{"type":"card","token":"tok_valid"},"shippingAddress":{"street":"123 Main St","city":"Seattle","zip":"98101"}}' \
  --cli-binary-format raw-in-base64-out \
  response.json && cat response.json

Enter fullscreen mode Exit fullscreen mode

Fail scenario:-

aws lambda invoke \
  --function-name durable-parallel-test:prod \
  --payload '{"orderId":"ORD-002","customerId":"CUST-123","items":[{"sku":"ITEM-1","qty":1,"price":50}],"paymentMethod":{"type":"card","token":"DECLINED"},"shippingAddress":{"street":"123 Main St","city":"Seattle","zip":"98101"}}' \
  --cli-binary-format raw-in-base64-out \
  response.json && cat response.json

Enter fullscreen mode Exit fullscreen mode

Invalid input:

aws lambda invoke \
  --function-name durable-parallel-test:prod \
  --payload '{"orderId":"","customerId":"","items":[],"paymentMethod":{"type":"card","token":"tok"},"shippingAddress":{"street":"","city":"","zip":""}}' \
  --cli-binary-format raw-in-base64-out \
  response.json && cat response.json

Enter fullscreen mode Exit fullscreen mode

Conclusion

Saga patterns make rollback to a consistent state very simple in distributed transactions. And with lambda durable functions’ checkpointing and retry capability, it is easy-peasy to implement. Thanks for reading😊