<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ idempotence - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ idempotence - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 28 Jun 2026 09:50:36 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/idempotence/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ The Saga Pattern in Node.js: How to Roll Back Distributed Transactions Across Microservices ]]>
                </title>
                <description>
                    <![CDATA[ Building reliable workflows across multiple microservices is challenging. In a monolith, a database transaction can ensure that multiple operations either succeed or fail together. But once data is sp ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-saga-pattern-in-node-js-roll-back-distributed-transactions-across-microservices/</link>
                <guid isPermaLink="false">6a2cfc9713c6ff659c6c31d1</guid>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ rollback ]]>
                    </category>
                
                    <category>
                        <![CDATA[ idempotence ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Md Tarikul Islam ]]>
                </dc:creator>
                <pubDate>Sat, 13 Jun 2026 06:45:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/b0e126ec-8b90-470a-b5c0-55e5e1673731.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building reliable workflows across multiple microservices is challenging. In a monolith, a database transaction can ensure that multiple operations either succeed or fail together. But once data is spread across different services and databases, that guarantee disappears.</p>
<p>This is where the Saga Pattern comes in. Instead of using distributed transactions, a saga coordinates a sequence of local transactions and runs compensation actions when something goes wrong.</p>
<p>In this article, we'll build an orchestrated Saga Pattern using NestJS, gRPC, PostgreSQL, and Sequelize. You'll learn how to coordinate work across services, implement compensation-based rollbacks, handle idempotency, and track workflow progress in a production-style microservice architecture.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-1-introduction">1. Introduction</a></p>
</li>
<li><p><a href="#heading-2-the-problem-in-one-picture">2. The Problem in One Picture</a></p>
</li>
<li><p><a href="#heading-3-why-you-need-a-saga">3. Why You Need a Saga</a></p>
</li>
<li><p><a href="#heading-4-choreography-vs-orchestration">4. Choreography vs Orchestration</a></p>
<ul>
<li><p><a href="#heading-choreography">Choreography</a></p>
</li>
<li><p><a href="#heading-orchestration">Orchestration</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-5-the-example-project">5. The Example Project</a></p>
</li>
<li><p><a href="#heading-6-architecture">6. Architecture</a></p>
</li>
<li><p><a href="#heading-7-the-saga-flow-step-by-step">7. The Saga Flow, Step by Step</a></p>
</li>
<li><p><a href="#heading-8-the-state-machine">8. The State Machine</a></p>
</li>
<li><p><a href="#heading-9-implementing-the-orchestrator">9. Implementing the Orchestrator</a></p>
<ul>
<li><p><a href="#heading-creating-the-saga-record">Creating the Saga Record</a></p>
</li>
<li><p><a href="#heading-the-main-loop">The Main Loop</a></p>
</li>
<li><p><a href="#heading-a-single-step-in-detail">A Single Step in Detail</a></p>
</li>
<li><p><a href="#heading-habits-worth-copying">Habits Worth Copying</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-10-implementing-the-participant">10. Implementing the Participant</a></p>
</li>
<li><p><a href="#heading-11-rollback-compensation">11. Rollback (Compensation)</a></p>
<ul>
<li><p><a href="#heading-on-the-orchestrator-side">On the Orchestrator Side</a></p>
</li>
<li><p><a href="#heading-on-the-participant-side">On the Participant Side</a></p>
</li>
<li><p><a href="#heading-rules-of-a-good-compensation">Rules of a Good Compensation</a></p>
</li>
<li><p><a href="#heading-what-happens-if-the-compensation-itself-fails">What Happens if the Compensation Itself Fails?</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-12-tracking-idempotency-and-observability">12. Tracking, Idempotency and Observability</a></p>
<ul>
<li><p><a href="#heading-orchestrator-side-agency_onboarding_sagas">Orchestrator Side — agency_onboarding_sagas</a></p>
</li>
<li><p><a href="#heading-participant-side-agency_provision_records">Participant Side — agency_provision_records</a></p>
</li>
<li><p><a href="#heading-observability-for-free">Observability for Free</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-13-testing-a-saga">13. Testing a Saga</a></p>
</li>
<li><p><a href="#heading-14-when-not-to-use-a-saga">14. When NOT to Use a Saga</a></p>
</li>
<li><p><a href="#heading-15-trade-offs-and-lessons-learned">15. Trade-offs and Lessons Learned</a></p>
</li>
<li><p><a href="#heading-16-conclusion">16. Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This article assumes you're already familiar with some backend development concepts. You don't need prior experience with the Saga Pattern, but you should be comfortable with:</p>
<ul>
<li><p>JavaScript, TypeScript, Node.js</p>
</li>
<li><p>NestJS fundamentals (controllers, services, dependency injection)</p>
</li>
<li><p>Basic PostgreSQL concepts</p>
</li>
<li><p>Database transactions</p>
</li>
<li><p>Docker (recommended for local development)</p>
</li>
<li><p>Microservice architecture basics</p>
</li>
<li><p>gRPC fundamentals (helpful but not required)</p>
</li>
</ul>
<p>If you've already built a few backend services with NestJS and PostgreSQL, you'll have everything you need to follow this guide.</p>
<h2 id="heading-1-introduction">1. Introduction</h2>
<p>A <strong>saga</strong> is a sequence of local transactions across multiple services. Each step commits its own database transaction. If a later step fails, the saga runs <strong>compensating transactions</strong> to semantically undo the work already committed.</p>
<p>The pattern was first described by Hector Garcia-Molina and Kenneth Salem in 1987 for long-lived database transactions. It was rediscovered a decade ago when companies started splitting monoliths into microservices and realised that the database transaction — the single most powerful tool in a backend developer's belt — stops working at the service boundary.</p>
<p>This article walks through an orchestrated saga in Node.js (NestJS + gRPC) for onboarding an agency, where two services must agree on a single business outcome:</p>
<ul>
<li><p><code>agency-service</code> — owns the agency record.</p>
</li>
<li><p><code>auth-service</code> — owns the organization, user and role.</p>
</li>
</ul>
<p>If either side fails, the system must end up as if nothing ever happened. No half-created users, orphan organizations, or 3am Slack threads.</p>
<h2 id="heading-2-the-problem-in-one-picture">2. The Problem in One Picture</h2>
<p>Here's the bug a saga is built to prevent:</p>
<pre><code class="language-plaintext">Step 1: auth-service     ✅ creates Organization #42
Step 2: auth-service     ✅ creates User #99
Step 3: agency-service   ❌ fails (DB down, validation, network blip…)

Result without a saga:
   Organization #42 and User #99 still exist.
   There is no Agency row.
   The user can log in but has nothing to manage.
   Support gets a ticket. Engineer writes a one-off SQL cleanup.
   Repeat every week.
</code></pre>
<p>The saga's job is to detect that step 3 failed and <strong>explicitly delete Organization #42 and User #99</strong>, so the system is consistent again — even though those rows live in a different service's database.</p>
<h2 id="heading-3-why-you-need-a-saga">3. Why You Need a Saga</h2>
<p>In a monolith, you wrap everything in one DB transaction and let the database handle atomicity:</p>
<pre><code class="language-ts">await sequelize.transaction(async (tx) =&gt; {
  await Organization.create({...}, { transaction: tx });
  await User.create({...}, { transaction: tx });
  await Agency.create({...}, { transaction: tx });
});
</code></pre>
<p>In microservices, each service has its own database. You can't wrap two services in one ACID transaction. The classic alternatives all have problems:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Problem</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Two-Phase Commit (2PC)</strong></td>
<td>Locks rows across services, coordinator is a single point of failure, and doesn't scale. Most modern databases don't support it well across HTTP/gRPC.</td>
</tr>
<tr>
<td><strong>"Just hope it works"</strong></td>
<td>Leaves orphan users / billing rows when half the flow fails. Real data corruption — and the longer the system runs, the more orphans accumulate.</td>
</tr>
<tr>
<td><strong>Manual cleanup scripts</strong></td>
<td>Works for a week. Bugs hide for months. New engineers don't know they exist.</td>
</tr>
<tr>
<td><strong>Eventual consistency without compensation</strong></td>
<td>Fine for some domains (analytics) but completely wrong for billing, identity, or anything with money.</td>
</tr>
<tr>
<td><strong>Saga pattern</strong></td>
<td>Each service commits locally. The orchestrator owns the workflow and runs explicit compensation on failure. It's auditable, restartable, and reasonable.</td>
</tr>
</tbody></table>
<p>The saga gives you eventual consistency with a clear, auditable rollback path — without distributed locks.</p>
<h2 id="heading-4-choreography-vs-orchestration">4. Choreography vs Orchestration</h2>
<p>There are two ways to implement a saga:</p>
<h3 id="heading-choreography">Choreography</h3>
<p>With Choreography, services emit events and other services subscribe and react.</p>
<pre><code class="language-plaintext">auth-service → emits "UserCreated"
agency-service → listens, creates agency, emits "AgencyCreated"
billing-service → listens, creates subscription…
</code></pre>
<p>It's simple at first, but brittle later. The workflow is scattered across N codebases. Nobody owns it. Debugging means tracing events across logs. Adding a step means changing several services.</p>
<h3 id="heading-orchestration">Orchestration</h3>
<p>With Orchestration, one service is the conductor. It calls the others in order.</p>
<pre><code class="language-plaintext">orchestrator:
   1. authClient.provisionAccount(...)
   2. agencyRepo.create(...)
   3. authClient.sendWelcomeEmail(...)
</code></pre>
<p>There's slightly more coupling here (the orchestrator imports clients), but the entire workflow lives in one file. Onboarding new engineers becomes a one-hour task. Adding a step is a single PR.</p>
<p><strong>Pick orchestration unless you have a strong reason not to.</strong> This article — and the reference implementation — uses orchestration.</p>
<h2 id="heading-5-the-example-project">5. The Example Project</h2>
<p>Our goal here is to create an Agency in the system. This is the moment a new B2B customer signs up.</p>
<p>It requires two services to agree on a single outcome:</p>
<p><code>auth-service</code> <strong>must create:</strong></p>
<ul>
<li><p>an <code>Organization</code> row (the tenant)</p>
</li>
<li><p>a <code>User</code> row (the agency admin who will log in)</p>
</li>
<li><p>a <code>UserRole</code> row linking the user to the <code>AGENCY_ADMIN</code> role</p>
</li>
</ul>
<p><code>agency-service</code> <strong>must create:</strong></p>
<ul>
<li>an <code>Agency</code> row containing business details (size, registration number, website, branches…), linked to the user/organization above</li>
</ul>
<p>These rows have foreign-key relationships <em>within</em> a service, but <em>not</em> across services — Postgres can't enforce that the user in auth's DB matches the <code>authUserId</code> in agency's DB. The application has to do it.</p>
<pre><code class="language-plaintext">auth-service DB                    agency-service DB
─────────────────                  ─────────────────
organizations  ◄────────┐
   │                    │
   │ (1:1)              │   foreign reference (no FK)
   ▼                    │           agencies
users  ──────► user_roles                     ─ authUserId
                                              └ authOrganizationId
</code></pre>
<p>If step 2 fails <em>after</em> step 1 succeeded, we end up with a user who can authenticate but has no agency — the exact bug from 2. That's what the saga prevents.</p>
<h2 id="heading-6-architecture">6. Architecture</h2>
<pre><code class="language-plaintext">                     ┌───────────────────────────────┐
                     │        API Gateway            │
                     └──────────────┬────────────────┘
                                    │ HTTP
                                    ▼
   ┌──────────────────────────────────────────────────┐
   │              agency-service                      │
   │   ┌─────────────────────────────────────────┐    │
   │   │   AgencyOnboardingOrchestrator (SAGA)   │    │
   │   └───────────────┬─────────────────────────┘    │
   │                   │ writes state                 │
   │                   ▼                              │
   │      agency_onboarding_sagas  (Postgres)         │
   └───────────────┬─────────────────┬────────────────┘
                   │ gRPC            │ gRPC
       provisionAgencyAccount   compensateAgencyAccount
                   │                 │
                   ▼                 ▼
   ┌──────────────────────────────────────────────────┐
   │              auth-service                        │
   │   AgencyProvisioningService  (Participant)       │
   │                                                  │
   │   organizations · users · user_roles             │
   │   agency_provision_records  ← idempotency log    │
   └──────────────────────────────────────────────────┘
</code></pre>
<p>Three components do all the work:</p>
<ol>
<li><p><code>AgencyOnboardingOrchestrator</code> in <code>agency-service</code> — drives the workflow.</p>
</li>
<li><p><code>agency_onboarding_sagas</code> table in <code>agency-service</code> — the durable log of the saga's progress.</p>
</li>
<li><p><code>AgencyProvisioningService</code> in <code>auth-service</code> — exposes a <code>do</code> operation (<code>provisionAgencyAccount</code>) and an <code>undo</code> operation (<code>compensateAgencyAccount</code>). It's backed by its own <code>agency_provision_records</code> idempotency table.</p>
</li>
</ol>
<p>The orchestrator never reaches into the auth database directly. The boundary is enforced by gRPC.</p>
<h2 id="heading-7-the-saga-flow-step-by-step">7. The Saga Flow, Step by Step</h2>
<p>This sequence diagram shows the complete lifecycle of the onboarding saga. The workflow begins when a client sends a request to create a new agency. The orchestrator first creates a saga record in its database and marks it as <code>STARTED</code>, giving it a durable record of the workflow before any business action takes place.</p>
<p>At a high level, the orchestrator begins by creating a saga record and then asks <code>auth-service</code> to provision the organization, user, and role. Once that succeeds, the orchestrator creates the agency record in its own database.</p>
<p>If every step succeeds, the saga reaches the <code>COMPLETED</code> state. If the agency creation fails after the auth resources have already been created, the orchestrator triggers a compensation step that instructs <code>auth-service</code> to remove everything it previously provisioned.</p>
<p>The key idea is that each service commits its own local transaction, while the saga coordinates the overall business workflow and ensures the system can return to a consistent state when failures occur.</p>
<pre><code class="language-mermaid">sequenceDiagram
    autonumber
    participant C as Client
    participant AS as agency-service&lt;br/&gt;Orchestrator
    participant DB1 as saga store
    participant AU as auth-service
    participant DB2 as auth DB

    C-&gt;&gt;AS: POST /agencies
    AS-&gt;&gt;DB1: INSERT saga (STARTED, payload)
    AS-&gt;&gt;AU: provisionAgencyAccount(sagaId, …)
    AU-&gt;&gt;DB2: BEGIN TX
    AU-&gt;&gt;DB2: create org + user + role + provision_record
    AU-&gt;&gt;DB2: COMMIT
    AU--&gt;&gt;AS: { userId, organizationId, roleId }
    AS-&gt;&gt;DB1: UPDATE saga (AUTH_PROVISIONED)
    AS-&gt;&gt;AS: create Agency row
    alt Agency row OK
        AS-&gt;&gt;DB1: UPDATE saga (AGENCY_CREATED → COMPLETED)
        AS-&gt;&gt;AU: sendAgencyWelcomeEmail (non-critical)
        AS--&gt;&gt;C: 200 OK + sagaId
    else Agency row fails
        AS-&gt;&gt;DB1: UPDATE saga (COMPENSATING)
        AS-&gt;&gt;AU: compensateAgencyAccount(sagaId)
        AU-&gt;&gt;DB2: BEGIN TX
        AU-&gt;&gt;DB2: delete role + token + user + org + record
        AU-&gt;&gt;DB2: COMMIT
        AS-&gt;&gt;DB1: UPDATE saga (COMPENSATED → FAILED)
        AS--&gt;&gt;C: 5xx + error code
    end
</code></pre>
<p>Read this once top to bottom and you'll understand the entire onboarding workflow. That's the value of orchestration — the sequence diagram <em>is</em> the architecture.</p>
<h2 id="heading-8-the-state-machine">8. The State Machine</h2>
<p>Every transition is written to <code>agency_onboarding_sagas</code> <strong>before</strong> the next step runs. That is what makes the saga observable and recoverable.</p>
<pre><code class="language-ts">export enum AgencyOnboardingSagaStatus {
  STARTED            = 'STARTED',            // Row exists, no side effects yet
  AUTH_PROVISIONED   = 'AUTH_PROVISIONED',   // Auth side committed
  AGENCY_CREATED     = 'AGENCY_CREATED',     // Agency row committed
  COMPLETED          = 'COMPLETED',          // Happy-path terminal state
  COMPENSATING       = 'COMPENSATING',       // Rollback in progress
  COMPENSATED        = 'COMPENSATED',        // Rollback finished
  FAILED             = 'FAILED',             // Terminal failure (with or without compensation)
}
</code></pre>
<p>Why so many states? Because <em>"what went wrong here?"</em> is a question someone will ask at 2am. A saga that only stores <code>success | failure</code> is useless for forensics.</p>
<pre><code class="language-plaintext">                ┌── auth fails ──────────► FAILED  (nothing to compensate)
                │
STARTED ──► AUTH_PROVISIONED ──► AGENCY_CREATED ──► COMPLETED  (happy path)
                                       │
                       agency fails ───┘
                                       ▼
                                COMPENSATING
                                       │
                                       ▼
                                COMPENSATED ──► FAILED  (consistent again)
</code></pre>
<p>The “point of no return” is <code>AUTH_PROVISIONED</code>. Before it, we can fail fast — there's nothing to undo. After it, every failure path <em>must</em> go through compensation.</p>
<h2 id="heading-9-implementing-the-orchestrator">9. Implementing the Orchestrator</h2>
<p>The orchestrator is the <em>only</em> place that knows the workflow. Each step is a private method, and each step persists its result before returning.</p>
<h3 id="heading-creating-the-saga-record">Creating the Saga Record</h3>
<pre><code class="language-ts">// agency-onboarding.saga.repository.ts
async createSaga(payload: CreateAgencyOrchestrationInput) {
  return this.sagaModel.create({
    sagaId: randomUUID(),                          // correlation id for everything
    status: AgencyOnboardingSagaStatus.STARTED,
    currentStep: 'STARTED',
    payload,                                       // full input snapshot for replay
  });
}
</code></pre>
<p>The <code>sagaId</code> is a UUID generated once and <strong>propagated to every downstream call</strong>. It's the single identifier that ties the saga log on the orchestrator side to the provision record on the participant side.</p>
<h3 id="heading-the-main-loop">The Main Loop</h3>
<pre><code class="language-ts">// agency-onboarding.orchestrator.ts (trimmed for the article)
async execute(input: CreateAgencyOrchestrationInput) {
  const saga = await this.sagaRepository.createSaga(input); // STARTED

  try {
    // Step 1 — auth-service work
    const authStep = await this.provisionAuth(saga, input);
    if (!authStep.ok) {
      await this.markFailed(saga, authStep.failure); // nothing to compensate
      return authStep.failure;
    }

    // Step 2 — agency-service work
    let activeSaga = authStep.saga; // status: AUTH_PROVISIONED
    try {
      activeSaga = await this.createAgencyRow(activeSaga, input, authStep.authIds);
    } catch (err) {
      // The expensive case: undo what auth-service did
      await this.compensateAuth(activeSaga, 'SAGA_FAILED');
      const failure = mapSagaFailure(err.message, 'SAGA_FAILED', 'CREATE_AGENCY');
      await this.markFailed(activeSaga, failure);
      return failure;
    }

    // Step 3 — mark done and run non-critical side effects
    activeSaga = await this.sagaRepository.updateSaga(activeSaga, {
      status: AgencyOnboardingSagaStatus.COMPLETED,
    });
    await this.sendWelcomeEmail(input, activeSaga); // best-effort

    return mapSagaSuccess(activeSaga, await this.agencyModel.findByPk(activeSaga.agencyId!));
  } catch (error) {
    // Defensive catch-all (lost DB connection, unexpected throw)
    await this.compensateAuth(saga, 'SAGA_FAILED');
    const failure = mapSagaFailure(error.message, 'SAGA_FAILED', 'SAGA');
    await this.markFailed(saga, failure);
    return failure;
  }
}
</code></pre>
<h3 id="heading-a-single-step-in-detail">A Single Step in Detail</h3>
<pre><code class="language-ts">private async provisionAuth(saga: AgencyOnboardingSaga, input: ...) {
  this.logger.log(`[${saga.sagaId}] PROVISION_AUTH`);

  const auth = await firstValueFrom(
    this.authClient.provisionAgencyAccount({
      sagaId: saga.sagaId,                  // &lt;-- correlation
      organizationName: input.agencyName.trim(),
      email: input.email.trim().toLowerCase(),
      // …
    }),
  );

  if (!auth.status || !auth.data) {
    return { ok: false, failure: mapAuthProvisionFailure(auth) };
  }

  // Persist the IDs we will need if we have to compensate later
  const updated = await this.sagaRepository.updateSaga(saga, {
    authOrganizationId: Number(auth.data.organizationId),
    authUserId: Number(auth.data.userId),
    authUserRoleId: Number(auth.data.userRoleId),
    status: AgencyOnboardingSagaStatus.AUTH_PROVISIONED,
  });

  return { ok: true, saga: updated, authIds: auth.data };
}
</code></pre>
<p>The line that does most of the work is the <code>updateSaga</code> call. It stores the foreign IDs returned by <code>auth-service</code> on the saga row, so even if the orchestrator process crashes and restarts, a recovery job can read that row and still know what to compensate.</p>
<h3 id="heading-habits-worth-copying">Habits Worth Copying</h3>
<ul>
<li><p><strong>Persist after every successful step</strong>, including the IDs you'll need to undo it.</p>
</li>
<li><p><strong>Distinguish critical vs non-critical steps.</strong> Welcome emails, audit logs and analytics events are <em>not</em> worth rolling a saga back for. They're best-effort.</p>
</li>
<li><p><strong>One log line per transition</strong>, prefixed with <code>[${sagaId}]</code>. Grep is your debugger.</p>
</li>
</ul>
<h2 id="heading-10-implementing-the-participant">10. Implementing the Participant</h2>
<p>The participant (<code>auth-service</code>) wraps all of its own work in a local DB transaction. Inside that boundary it's still ACID — the saga only handles the cross-service problem.</p>
<pre><code class="language-ts">// agency-provisioning.service.ts (trimmed)
async provisionAgencyAccount(req: ProvisionAgencyAccountInput) {

  // 1. Idempotency — return the previous result if this sagaId already provisioned.
  const existing = await this.provisionRecordModel.findOne({
    where: { sagaId: req.sagaId },
  });
  if (existing) {
    return serviceSuccess('Agency admin already onboarded', {
      userId: Number(existing.userId),
      organizationId: Number(existing.organizationId),
      userRoleId: Number(existing.roleId),
    });
  }

  // 2. Domain validation BEFORE the transaction (fail fast).
  if (await this.emailExists(req.email)) {
    return serviceFailure('Email already exists', { code: 'EMAIL_EXISTS' });
  }
  if (await this.organizationExists(req.organizationName)) {
    return serviceFailure('Organization already exists', { code: 'ORGANIZATION_EXISTS' });
  }

  // 3. The actual work — atomic at the auth-service boundary.
  return withSequelizeTransaction(this.sequelize, async (tx) =&gt; {
    const org = await this.organizationModel.create({ ... }, { transaction: tx });
    const user = await this.userModel.create({ ..., organizationId: org.id }, { transaction: tx });
    await this.userRoleModel.create({ userId: user.id, roleId: agencyAdminRole.id }, { transaction: tx });

    // The audit record that makes compensation possible later.
    await this.provisionRecordModel.create(
      { sagaId: req.sagaId, organizationId: org.id, userId: user.id, roleId: agencyAdminRole.id },
      { transaction: tx },
    );

    return serviceSuccess('Provisioned', {
      userId: user.id, organizationId: org.id, userRoleId: agencyAdminRole.id,
    });
  });
}
</code></pre>
<p>Three things make this method "saga-safe":</p>
<ol>
<li><p><strong>Idempotency check first:</strong> If the orchestrator retries (network blip, gRPC timeout), the second call is a no-op that returns the same IDs. No duplicate users.</p>
</li>
<li><p><strong>Validation outside the transaction:</strong> Cheap reads first, expensive writes second.</p>
</li>
<li><p><strong>One transaction wraps every write:</strong> If any insert fails, the whole thing rolls back automatically. The orchestrator sees a clean failure response and knows nothing was persisted.</p>
</li>
</ol>
<p>The <code>agency_provision_records</code> table is the single most important piece of the participant. It's <strong>both</strong> the idempotency key <em>and</em> the compensation lookup — keyed by the same <code>sagaId</code> the orchestrator uses.</p>
<h2 id="heading-11-rollback-compensation">11. Rollback (Compensation)</h2>
<p>Compensation is just another gRPC call. The orchestrator sends the <code>sagaId</code> and the IDs it remembers. The participant deletes everything it created, <strong>in reverse dependency order</strong>, inside its own DB transaction.</p>
<h3 id="heading-on-the-orchestrator-side">On the Orchestrator Side</h3>
<pre><code class="language-ts">private async compensateAuth(saga: AgencyOnboardingSaga, errorCode?: string) {
  if (!saga.authUserId &amp;&amp; !saga.authOrganizationId) {
    // Nothing was provisioned — nothing to compensate.
    return;
  }

  // Mark the saga as compensating BEFORE the call, so the row is consistent
  // even if the compensating RPC times out.
  await this.sagaRepository.updateSaga(saga, {
    status: AgencyOnboardingSagaStatus.COMPENSATING,
    currentStep: 'COMPENSATING',
    errorCode,
  });

  try {
    const rollback = await firstValueFrom(this.authClient.compensateAgencyAccount({
      sagaId: saga.sagaId,
      organizationId: saga.authOrganizationId,
      userId: saga.authUserId,
    }));
    if (!rollback.status) {
      this.logger.error(`[\({saga.sagaId}] Auth compensation returned failure: \){rollback.message}`);
    }
  } catch (err) {
    this.logger.error(`[\({saga.sagaId}] Auth compensation RPC failed: \){err.message}`);
  }

  await this.sagaRepository.updateSaga(saga, {
    status: AgencyOnboardingSagaStatus.COMPENSATED,
    currentStep: 'COMPENSATED',
  });
}
</code></pre>
<h3 id="heading-on-the-participant-side">On the Participant Side</h3>
<pre><code class="language-ts">private async rollbackProvisionedAuth(req, sagaId: string, tx: Transaction) {
  // Use the saga log as the source of truth — even if the caller forgot IDs.
  const record = await this.provisionRecordModel.findOne({
    where: { sagaId }, transaction: tx,
  });
  const userId         = req.userId         ?? record?.userId;
  const organizationId = req.organizationId ?? record?.organizationId;

  if (userId) {
    const user = await this.userModel.findByPk(userId, { transaction: tx, attributes: ['email'] });
    await this.userRoleModel.destroy({ where: { userId }, transaction: tx });
    if (user?.email) {
      await this.passwordResetTokenModel.destroy({ where: { email: user.email }, transaction: tx });
    }
    await this.userModel.destroy({ where: { id: userId }, transaction: tx });
  }
  if (organizationId) {
    await this.organizationModel.destroy({ where: { id: organizationId }, transaction: tx });
  }
  if (record) {
    await record.destroy({ transaction: tx });
  }
}
</code></pre>
<h3 id="heading-rules-of-a-good-compensation">Rules of a Good Compensation</h3>
<ol>
<li><p><strong>Reverse the order of creation:</strong> Children first (user_roles, tokens), then parents (users, organizations). The same rule you follow for <code>DROP TABLE</code> statements.</p>
</li>
<li><p><strong>Be idempotent:</strong> Receiving the same <code>sagaId</code> twice must be safe — every <code>destroy</code> is a no-op if the row is already gone.</p>
</li>
<li><p><strong>Use the saga log, not just the request:</strong> If the caller forgets an ID or sends a partial payload, look it up by <code>sagaId</code>. Defence in depth.</p>
</li>
<li><p><strong>Wrap it in a local transaction:</strong> The rollback must itself be atomic — half-undone is worse than not-undone.</p>
</li>
<li><p><strong>Always close the loop on the orchestrator side:</strong> Mark <code>COMPENSATED</code> even if the RPC failed. The failure should also be surfaced (log, metric, alert). A stuck <code>COMPENSATING</code> row is an operational landmine.</p>
</li>
</ol>
<h3 id="heading-what-happens-if-the-compensation-itself-fails">What Happens if the Compensation Itself Fails?</h3>
<p>This is the worst case in any saga design. There are three reasonable strategies:</p>
<p>First, you can retry with exponential backoff. This works for transient failures (network, deadlocks).</p>
<p>Second, you can dead-letter the saga — write it to a "needs human attention" queue and alert.</p>
<p>Third, you can expose a manual rollback endpoint. This reference implementation does that via <code>RollbackAgencyOnboarding</code> gRPC, so an operator can replay compensation with the same <code>sagaId</code>.</p>
<p>A production system should combine all three. The pattern doesn't decide for you. <em>You</em> decide based on your business risk.</p>
<h2 id="heading-12-tracking-idempotency-and-observability">12. Tracking, Idempotency and Observability</h2>
<p>Two tables, both keyed by the same UUID <code>sagaId</code>, give you full traceability across services.</p>
<h3 id="heading-orchestrator-side-agencyonboardingsagas">Orchestrator Side — <code>agency_onboarding_sagas</code></h3>
<table>
<thead>
<tr>
<th>column</th>
<th>purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>sagaId</code> (UUID, unique)</td>
<td>Propagated to every RPC. The join key across services.</td>
</tr>
<tr>
<td><code>status</code></td>
<td>Current state in the state machine.</td>
</tr>
<tr>
<td><code>currentStep</code></td>
<td>Human-readable label for dashboards (<code>PROVISION_AUTH</code>, <code>CREATE_AGENCY</code>…).</td>
</tr>
<tr>
<td><code>payload</code> (JSONB)</td>
<td>Snapshot of the input — used for replay, debug, support.</td>
</tr>
<tr>
<td><code>authOrganizationId</code>, <code>authUserId</code>, <code>authUserRoleId</code></td>
<td>Foreign IDs needed for compensation.</td>
</tr>
<tr>
<td><code>agencyId</code></td>
<td>Set once the agency row exists.</td>
</tr>
<tr>
<td><code>errorCode</code>, <code>errorMessage</code></td>
<td>Filled on failure.</td>
</tr>
<tr>
<td><code>createdAt</code>, <code>updatedAt</code></td>
<td>Timeline for the saga.</td>
</tr>
</tbody></table>
<p>A real row in <code>COMPLETED</code> state looks roughly like this:</p>
<pre><code class="language-json">{
  "sagaId": "0a4f3e2c-7b11-4f8d-9a2c-90b6f5f5b8a1",
  "status": "COMPLETED",
  "currentStep": "COMPLETED",
  "agencyId": 17,
  "authOrganizationId": 42,
  "authUserId": 99,
  "authUserRoleId": 3,
  "errorCode": null,
  "errorMessage": null,
  "payload": { "agencyName": "Acme Education", "email": "admin@acme.com", "...": "..." },
  "createdAt": "2026-05-22T10:14:32.118Z",
  "updatedAt": "2026-05-22T10:14:33.412Z"
}
</code></pre>
<h3 id="heading-participant-side-agencyprovisionrecords">Participant Side — <code>agency_provision_records</code></h3>
<table>
<thead>
<tr>
<th>column</th>
<th>purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>sagaId</code> (unique)</td>
<td>Idempotency key. The same <code>sagaId</code> from the orchestrator.</td>
</tr>
<tr>
<td><code>userId</code>, <code>organizationId</code>, <code>roleId</code></td>
<td>What to delete on compensation.</td>
</tr>
<tr>
<td><code>createdAt</code>, <code>updatedAt</code></td>
<td>Audit timestamps.</td>
</tr>
</tbody></table>
<h3 id="heading-observability-for-free">Observability for Free</h3>
<p>Because every log line is prefixed with <code>[${sagaId}]</code>, a single grep across both services gives the full timeline:</p>
<pre><code class="language-plaintext">[0a4f3e2c…] PROVISION_AUTH                  agency-service
[0a4f3e2c…] provisionAgencyAccount: ok      auth-service
[0a4f3e2c…] CREATE_AGENCY                   agency-service
[0a4f3e2c…] Agency step failed: ...         agency-service
[0a4f3e2c…] Auth compensation completed     auth-service
</code></pre>
<p>In a structured-logging setup (Loki, Elasticsearch, Datadog) this becomes a one-click filter. <strong>The</strong> <code>sagaId</code> <strong>is your distributed trace.</strong></p>
<h2 id="heading-13-testing-a-saga">13. Testing a Saga</h2>
<p>A saga is just a state machine, so the test matrix is finite and small. Cover at least these cases:</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Scenario</th>
<th>Expected end state</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Happy path</td>
<td><code>COMPLETED</code>, agency exists, user exists</td>
</tr>
<tr>
<td>2</td>
<td>Auth step fails (e.g. email exists)</td>
<td><code>FAILED</code>, no rows on either side</td>
</tr>
<tr>
<td>3</td>
<td>Agency step fails</td>
<td><code>COMPENSATED</code>, auth rows gone, no agency</td>
</tr>
<tr>
<td>4</td>
<td>Compensation RPC times out</td>
<td><code>COMPENSATING</code> → operator-driven recovery</td>
</tr>
<tr>
<td>5</td>
<td>Caller retries with the same <code>sagaId</code></td>
<td>Second call returns the first call's result; no duplicate rows</td>
</tr>
<tr>
<td>6</td>
<td>Welcome email fails</td>
<td><code>COMPLETED</code> still — non-critical step did not cascade</td>
</tr>
</tbody></table>
<p>Two practical tips for testing:</p>
<p>First, mock the gRPC client at the orchestrator level, not the network. You want to assert that <code>compensateAgencyAccount</code> <em>was called with the right</em> <code>sagaId</code>, not that bytes hit a socket.</p>
<p>Second, spin up a real Postgres in integration tests (Testcontainers, or a Docker Compose <code>postgres</code> service). The saga state machine is too easy to "test" against a mock and too easy to break against a real DB.</p>
<h2 id="heading-14-when-not-to-use-a-saga">14. When NOT to Use a Saga</h2>
<p>Sagas are not free. Skip them when:</p>
<ul>
<li><p><strong>One service does all the writes.</strong> Use a regular DB transaction. Don't reinvent the wheel.</p>
</li>
<li><p><strong>The workflow is read-only or analytical.</strong> No rollback semantics exist for a SELECT.</p>
</li>
<li><p><strong>The "rollback" is impossible.</strong> You sent a real email. You charged a credit card and the gateway doesn't support refunds. In those cases, design forward: send an apology email, queue a manual refund. Sagas can't unsend physical actions.</p>
</li>
<li><p><strong>You don't actually have multiple services yet.</strong> A saga in a monolith is over-engineering. Wait until the service boundary is real.</p>
</li>
</ul>
<p>A saga adds a state table, a compensation method per step, and an operational habit of grepping by <code>sagaId</code>. That cost is worth paying when the alternative is orphaned data — and not before.</p>
<h2 id="heading-15-trade-offs-and-lessons-learned">15. Trade-offs and Lessons Learned</h2>
<p>Things that worked well in this design:</p>
<ul>
<li><p>Synchronous orchestration is easier to debug than choreography. A new engineer reads one file and understands the whole flow.</p>
</li>
<li><p>Idempotency at the participant is non-negotiable. Retries from the orchestrator must be safe. Build it in from day one — retro-fitting is painful.</p>
</li>
<li><p>The saga table replaces tribal knowledge. Ops can answer <em>"what happened to this signup?"</em> with a single SQL query. The payload JSONB is gold during incidents.</p>
</li>
<li><p><code>sagaId</code> as the trace key plays nicely with OpenTelemetry / Datadog / Loki — no extra infra to set up.</p>
</li>
</ul>
<p>Things to know before copying this pattern:</p>
<ul>
<li><p>A failing compensation is the worst case. If <code>compensateAgencyAccount</code> itself errors, you have inconsistent state. Plan for retries + dead-letter + a manual rollback endpoint from the start.</p>
</li>
<li><p>Non-critical steps must be marked explicitly. Here, the welcome email is allowed to fail without rolling back the agency. Don't accidentally compensate over a flaky SMTP provider.</p>
</li>
<li><p>Sagas aren't a replacement for local transactions. Inside each service, still use a real DB transaction. The saga only handles the cross-service seam.</p>
</li>
<li><p>Synchronous gRPC is simple but couples availability. If <code>auth-service</code> is down, agency creation fails. Swap the gRPC calls for a durable message bus (RabbitMQ / Kafka) and treat each step as a command + reply when you need higher resilience.</p>
</li>
<li><p>The orchestrator becomes a critical service. Treat its uptime accordingly — monitor saga durations, alert on stuck <code>COMPENSATING</code> rows, and run more than one replica.</p>
</li>
</ul>
<h2 id="heading-16-conclusion">16. Conclusion</h2>
<p>The saga pattern isn't magic. It's a disciplined version of what experienced engineers already do by hand: <em>commit locally, record what you did, and know how to undo it.</em></p>
<p>In Node.js with NestJS, you only need three ingredients:</p>
<ol>
<li><p><strong>A state table</strong> to track the saga.</p>
</li>
<li><p><strong>An orchestrator</strong> that drives the workflow and writes that state.</p>
</li>
<li><p><strong>A participant</strong> that exposes a <code>do</code> and an <code>undo</code> operation, both idempotent and keyed by <code>sagaId</code>.</p>
</li>
</ol>
<p>Get those three right and your microservices can offer the same "all-or-nothing" feel as a monolithic transaction — without the operational pain of distributed locks.</p>
<p>Start simple, use orchestration, make every step idempotent, persist before you call, and always know how to undo. That's the whole pattern.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Idempotence? Explained with Real-World Examples ]]>
                </title>
                <description>
                    <![CDATA[ Idempotence is a property of an operation that ensures that, if the operation is repeated once or more than once, you get the same result. Apply it once or more and the outcome's the same, idempotence is the name. All rhyming aside, idempotence is an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/idempotence-explained/</link>
                <guid isPermaLink="false">66d45e177df3a1f32ee7f80d</guid>
                
                    <category>
                        <![CDATA[ idempotence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Thu, 29 Feb 2024 00:44:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/cover--2-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Idempotence is a property of an operation that ensures that, if the operation is repeated once or more than once, you get the same result.</p>
<p>Apply it once or more and the outcome's the same, idempotence is the name.</p>
<p>All rhyming aside, idempotence is an important concept often used in the design of everyday things. The underlying principle of idempotence – where repeated actions do not change the outcome, beyond the initial action – has been applied implicitly or explicitly both to the physical world and the digital world of cloud computing and software applications.</p>
<p>This article will show you some examples of idempotence in the physical world, as well as how it is used in software architectures to build reliable and fault-tolerant systems.</p>
<h2 id="heading-idempotence-in-the-physical-world">Idempotence in the Physical World</h2>
<p>Idempotent buttons are used in everyday systems, like traffic light buttons, the stop button on London buses, and elevator call buttons.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07003d3-ffc7-4074-88d1-cee009fb7119_2374x1308.png" alt="Image" width="1456" height="802" loading="lazy"></p>
<p><em>Some examples of idempotence in the physical world: a traffic light button for pedestrians, a stop button in a London bus, and an elevator call button.</em></p>
<p>Pressing a traffic light button multiple times does not make the light change faster – it simply registers the need for a pedestrian crossing once.</p>
<p>Similarly, pressing the stop button on a London bus signals the driver to stop at the next stop – but pressing it multiple times does not change the bus's route, make the bus stop faster, or cancel the initial stop request.</p>
<h2 id="heading-idempotence-patterns-in-software-architectures">Idempotence Patterns in Software Architectures</h2>
<p>Different patterns of idempotence are used in software architectures. We'll discuss two popular ones here.</p>
<h3 id="heading-api-design">API Design</h3>
<p>In REST APIs, HTTP methods like GET, HEAD, PUT, and DELETE are inherently idempotent.</p>
<ul>
<li><p>GET: Used to retrieve data from a server. Multiple GET requests to the same resource are safe and should return the same data, assuming no changes have been made to the resource in the meantime.</p>
</li>
<li><p>HEAD: Similar to GET, but it retrieves only the header information about a resource. Since it does not return a body, it's inherently safe and idempotent.</p>
</li>
<li><p>PUT: Replaces a resource's current representation with the request payload. Repeatedly putting the same data to the same resource endpoint will leave the resource in the same state.</p>
</li>
<li><p>DELETE: Removes a resource. Deleting the same resource multiple times results in the same outcome: the resource is removed after the first successful request, and subsequent DELETE requests typically return a 404 Not Found or 204 No Content status, indicating that there's no resource to delete.</p>
</li>
</ul>
<p>A POST operation is not inherently idempotent, since it is typically used to create resources. But some implementations of POST can be designed to be idempotent.</p>
<p>A good example of this is the Post/Redirect/Get pattern, also referred to as the PRG pattern. This pattern is particularly useful for handling form submissions and can mitigate issues caused by users refreshing or bookmarking pages that make changes to the server's state. Let’s examine in detail how this works.</p>
<h4 id="heading-how-to-make-a-post-operation-idempotent">How to Make a POST Operation Idempotent</h4>
<p>The sequence diagram below explains how the PRG pattern works to prevent duplicate orders in an e-commerce web application:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf24083-d54b-4c97-8607-bebe4955fe71_1044x774.png" alt="Image" width="1044" height="774" loading="lazy"></p>
<p><em>Sequence diagram showing how the PRG pattern works to "convert" a POST operation to an idempotent GET operation</em></p>
<ol>
<li><p><strong>Post</strong>: When the user submits a form to place an order, the browser sends a POST request to the server.</p>
</li>
<li><p><strong>Redirect</strong>: After the server processes the POST request (for example, placing the order), it sends a redirect response to the browser, usually with a 303/302 HTTP status code, directing it to a new URL. This URL is typically an order confirmation page.</p>
</li>
<li><p><strong>Get</strong>: The browser then makes a GET request to the URL provided by the redirect. The user sees the page that confirms their order or brings them back to a safe state where no duplicate orders can be accidentally created.</p>
</li>
</ol>
<p>The key benefit of using the PRG pattern is that it turns the POST request into a GET request, which is idempotent. This means that refreshing the page at the end of the process will not cause the same order to be submitted more than once, because refreshing will only repeat the GET request, not the initial POST request that submitted the order.</p>
<p>This pattern enhances the user experience by preventing common mistakes, such as double-clicking a submission button or refreshing the page, creating unwanted duplicate orders.</p>
<p>It also makes the application more robust and user-friendly, as users can safely refresh the confirmation page or bookmark it without worrying about the order being duplicated.</p>
<h3 id="heading-message-queueing-systems">Message Queueing Systems</h3>
<p>A message queue can contribute to making a system idempotent by ensuring that even if a message (representing a request) is delivered multiple times, the operation it triggers is executed only once, or its effect is the same regardless of how many times it's executed.</p>
<p>This is crucial in distributed systems where network failures, system crashes, or other issues can lead to the same message being processed multiple times.</p>
<p>Let’s look at an example that involves making a payment. No customer wants to be accidentally double-charged when making a purchase, so making sure the system is idempotent is very important.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37b5e26b-6892-48c7-8aa8-eaa0c2a39be5_1784x1530.png" alt="Image" width="1456" height="1249" loading="lazy"></p>
<p><em>Sequence diagram showing how a message queue can make a system idempotent and prevent duplicate payments from happening</em></p>
<ol>
<li><p>The message queue sends a message to the payment system to debit an account.</p>
</li>
<li><p>This message has a unique Transaction ID. The Database of Processed IDs maintains a record of Transaction IDs that have been processed. The Payment System checks the Transaction ID against the Database of Processed IDs and checks if this payment has already been processed.</p>
</li>
<li><p>If the message has a transaction ID in the Processed ID Database, it is ignored and treated as already processed. The payment has already been made and does not need to be repeated. The Payment System sends an acknowledgement (ACK) back to the queue to inform it that the message has been ignored. The message queue needs to know that the message has been handled before it deletes the message on its side.</p>
</li>
<li><p>If the message does not have a Transaction ID in the Processed ID database, this means the payment has not been processed before. The payment is therefore processed and the transaction ID is added to the Database of Processed IDs. Ideally, these two steps should be done in a single <a target="_blank" href="https://lightcloud.substack.com/i/140524854/atomicity">atomic transaction</a>. This prevents an unwanted state where the payment is processed but the Transaction ID is never added to the Database of Processed IDs because of a database failure, networking issue or any other fault.</p>
</li>
<li><p>In the final step, the Payment System sends an acknowledgement (ACK) back to the queue to inform it that the message has been successfully processed. This acknowledgement informs the queue that the message has been successfully received, processed, and no longer needs to be kept in the queue for future delivery. This prevents the message from being sent again, ensuring that the system is idempotent.</p>
</li>
</ol>
<p>In this example, the system ensures idempotency by:</p>
<ul>
<li><p>Checking Transaction IDs for new payments against a database of payments already made</p>
</li>
<li><p>Sending the acknowledgement to the queue after the message is ignored or processed by the Payment System. Thus ensures that the same message is only sent to the Payment System once.</p>
</li>
</ul>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>Idempotence solves one fundamental problem: how do you handle operations that, intentionally or by accident, can be repeated? Idempotence ensures that no matter how many times an operation is applied, the outcome remains the same after the first application, mitigating the risks associated with repeated actions.</p>
<p>The underlying principle of idempotence is used in the design of everyday objects we interact with in the physical world, from traffic light buttons for pedestrians to elevator call buttons.</p>
<p>In the abstract world of software architecture, idempotence ensures that repeated operations have the same effect as performing that operation just once. Idempotence allows us to build reliable and fault-tolerant architectures.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
