<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammad Dadashi</title>
    <description>The latest articles on DEV Community by Mohammad Dadashi (@dadashi).</description>
    <link>https://dev.to/dadashi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3677044%2F60827052-92e3-4acb-ba58-1b6d4f57121d.jpeg</url>
      <title>DEV Community: Mohammad Dadashi</title>
      <link>https://dev.to/dadashi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dadashi"/>
    <language>en</language>
    <item>
      <title>Every Distributed Payment System Lies: How We Prevent Money Loss</title>
      <dc:creator>Mohammad Dadashi</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:25:18 +0000</pubDate>
      <link>https://dev.to/dadashi/every-distributed-payment-system-lies-how-we-prevent-money-loss-1i60</link>
      <guid>https://dev.to/dadashi/every-distributed-payment-system-lies-how-we-prevent-money-loss-1i60</guid>
      <description>&lt;p&gt;"The customer was charged, but the order doesn't exist."&lt;br&gt;
If you've worked on payment systems long enough, you've probably heard a sentence like this.&lt;br&gt;
It usually starts with a timeout.&lt;br&gt;
The payment gateway successfully charges the customer's card.&lt;br&gt;
Right before our service commits the database transaction, Kubernetes kills the pod.&lt;br&gt;
The mobile application never receives a response.&lt;br&gt;
After three seconds, it retries the request.&lt;br&gt;
Congratulations.&lt;br&gt;
You just charged the customer twice.&lt;br&gt;
This is why experienced backend engineers don't chase exactly-once processing. We build systems that behave as if operations happened exactly once—even when the network retries requests, brokers redeliver messages, or services crash halfway through a transaction.&lt;br&gt;
This article covers the patterns we've used to build resilient financial systems that don't lose money when distributed systems inevitably fail.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Myth of Exactly Once
&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions in distributed systems is that "exactly once" is something you can simply enable.&lt;/p&gt;

&lt;p&gt;You can't.&lt;/p&gt;

&lt;p&gt;Every network call can experience one of three outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request never reaches the server.&lt;/li&gt;
&lt;li&gt;The request succeeds.&lt;/li&gt;
&lt;li&gt;The request succeeds, but the response never reaches the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third scenario causes most production incidents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   │ Payment Request
   ▼
Payment Service
   │
   │ Charge Card
   ▼
Payment Gateway
   │
   │ Success
   ▲
   │
Network Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the client's perspective, the request failed.&lt;/p&gt;

&lt;p&gt;From the payment gateway's perspective, it succeeded.&lt;/p&gt;

&lt;p&gt;Who is right?&lt;/p&gt;

&lt;p&gt;Both.&lt;/p&gt;

&lt;p&gt;That's why clients retry.&lt;/p&gt;

&lt;p&gt;And retries are exactly where duplicate payments begin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retries Are Good. Non-Idempotent APIs Are Dangerous
&lt;/h2&gt;

&lt;p&gt;Imagine this endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/payments"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PaymentResponse&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;PaymentRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;paymentService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client retries.&lt;/p&gt;

&lt;p&gt;The controller executes again.&lt;/p&gt;

&lt;p&gt;The payment gateway receives another charge request.&lt;/p&gt;

&lt;p&gt;Nothing prevented it.&lt;/p&gt;

&lt;p&gt;The bug wasn't the retry.&lt;/p&gt;

&lt;p&gt;The bug was assuming the request would only arrive once.&lt;/p&gt;

&lt;p&gt;Every API handling financial operations should assume the same request may arrive multiple times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotency Is Your First Line of Defense
&lt;/h2&gt;

&lt;p&gt;The simplest solution is surprisingly effective.&lt;/p&gt;

&lt;p&gt;Every payment request carries a unique &lt;strong&gt;Idempotency-Key&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /payments

Idempotency-Key:
4f6a6d4d-8d8f-4d1d-89cf-29d51d42d667
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before processing the payment, we check whether this key already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Exists?
Request ─────► Database
                 │
         ┌───────┴────────┐
         │                │
       Yes               No
         │                │
Return saved       Process payment
response            Save result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified implementation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PaymentResponse&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PaymentRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;PaymentResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentRecord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the client can retry five times.&lt;/p&gt;

&lt;p&gt;The payment is still executed once.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem Starts After the Payment
&lt;/h2&gt;

&lt;p&gt;Many engineers stop after implementing idempotency.&lt;/p&gt;

&lt;p&gt;That isn't enough.&lt;/p&gt;

&lt;p&gt;Consider this sequence.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Charge customer&lt;/li&gt;
&lt;li&gt;Update database&lt;/li&gt;
&lt;li&gt;Publish PaymentCompleted event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What happens if the application crashes here?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Charge Card ✔

Save Database ✔

Publish Event ✖
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Money has been collected.&lt;/p&gt;

&lt;p&gt;No other service knows about it.&lt;/p&gt;

&lt;p&gt;Inventory is never reserved.&lt;/p&gt;

&lt;p&gt;Email is never sent.&lt;/p&gt;

&lt;p&gt;Order status remains "Pending."&lt;/p&gt;

&lt;p&gt;The database and the rest of the system now disagree.&lt;/p&gt;

&lt;p&gt;This is called the dual-write problem, and nearly every distributed system encounters it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Transactional Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;Instead of writing directly to Kafka or RabbitMQ, we first write the event into the same database transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| Payments Table       |
+----------------------+

+----------------------+
| Outbox Table         |
+----------------------+

       Same Transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;completePayment&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;paymentRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markCompleted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;outboxRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentCompletedEvent&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, a background publisher reads the Outbox table and publishes events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database

Payments
Outbox
   │
   ▼

Publisher

   │

Kafka

   │

Consumers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database and the event are committed together.&lt;/p&gt;

&lt;p&gt;If the service crashes, the event is still safely waiting in the Outbox table.&lt;/p&gt;

&lt;p&gt;Nothing gets lost.&lt;/p&gt;




&lt;h2&gt;
  
  
  But Wait... What If Kafka Receives the Same Event Twice?
&lt;/h2&gt;

&lt;p&gt;It absolutely can.&lt;/p&gt;

&lt;p&gt;Imagine this timeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Publisher
     │
     │ Send Event
     ▼
Kafka

Publisher crashes

Database still says:
Published = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the publisher restarts, it sends the event again.&lt;/p&gt;

&lt;p&gt;This is expected.&lt;/p&gt;

&lt;p&gt;Outbox solves lost events.&lt;/p&gt;

&lt;p&gt;It does not eliminate duplicates.&lt;/p&gt;

&lt;p&gt;That responsibility belongs to the consumer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Consumers Must Be Idempotent Too
&lt;/h2&gt;

&lt;p&gt;Every event should contain a unique identifier.&lt;/p&gt;

&lt;p&gt;Before processing, consumers check whether that identifier has already been handled.&lt;/p&gt;

&lt;p&gt;Redis makes this extremely fast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIfAbsent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
             &lt;span class="n"&gt;messageId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
             &lt;span class="s"&gt;"DONE"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
             &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofHours&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FALSE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Redis already contains the key, the event is ignored.&lt;/p&gt;

&lt;p&gt;The message broker can deliver the same event multiple times without producing duplicate business operations.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What About Distributed Transactions?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine a payment flow involving four services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment

↓

Fraud Check

↓

Inventory

↓

Shipping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose Shipping fails.&lt;/p&gt;

&lt;p&gt;Do we keep the payment?&lt;/p&gt;

&lt;p&gt;Do we release inventory?&lt;/p&gt;

&lt;p&gt;Do we refund?&lt;/p&gt;

&lt;p&gt;Traditional distributed transactions (2PC) attempt to solve this problem by locking every participant.&lt;/p&gt;

&lt;p&gt;They also introduce blocking, coordinator failures, and poor scalability.&lt;/p&gt;

&lt;p&gt;Modern fintech systems generally prefer &lt;strong&gt;Saga&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of one giant transaction, every service performs its own local transaction.&lt;/p&gt;

&lt;p&gt;If something fails later, compensating actions reverse the completed work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Charge Payment ✔

Reserve Inventory ✔

Shipping ✖

↓

Refund Payment

↓

Release Inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system is temporarily inconsistent, but eventually reaches a valid state.&lt;/p&gt;

&lt;p&gt;That's a much better trade-off than globally locking every database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability Is Part of the Design
&lt;/h2&gt;

&lt;p&gt;Resilience isn't only about preventing failures.&lt;/p&gt;

&lt;p&gt;It's about understanding failures quickly.&lt;/p&gt;

&lt;p&gt;Every payment request should carry a correlation ID across every service.&lt;/p&gt;

&lt;p&gt;Every metric should answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many retries happened today?&lt;/li&gt;
&lt;li&gt;How many duplicate requests were ignored?&lt;/li&gt;
&lt;li&gt;How many Outbox events are waiting?&lt;/li&gt;
&lt;li&gt;What's the Kafka consumer lag?&lt;/li&gt;
&lt;li&gt;Which payment failed, and where?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A production payment service without tracing is like trying to investigate a bank robbery with no cameras.&lt;/p&gt;

&lt;p&gt;OpenTelemetry, Prometheus, Grafana, and structured logging are not optional additions.&lt;/p&gt;

&lt;p&gt;They're part of the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Checklist
&lt;/h2&gt;

&lt;p&gt;Before calling a payment service "production ready," I expect to see most of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every write endpoint is idempotent.&lt;/li&gt;
&lt;li&gt;Retry policies use exponential backoff.&lt;/li&gt;
&lt;li&gt;Database writes and events use the Transactional Outbox pattern.&lt;/li&gt;
&lt;li&gt;Consumers implement deduplication.&lt;/li&gt;
&lt;li&gt;Dead Letter Queues exist for unrecoverable failures.&lt;/li&gt;
&lt;li&gt;Correlation IDs flow through every service.&lt;/li&gt;
&lt;li&gt;Metrics expose retries, failures, queue lag, and latency.&lt;/li&gt;
&lt;li&gt;Distributed tracing is enabled.&lt;/li&gt;
&lt;li&gt;Chaos testing verifies retry behavior.&lt;/li&gt;
&lt;li&gt;Integration tests simulate broker failures and duplicate deliveries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If even one item is missing, failures are usually a matter of when, not if.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;One of the biggest lessons I learned while building distributed systems is that reliability doesn't come from perfect infrastructure.&lt;/p&gt;

&lt;p&gt;It comes from assuming that infrastructure will eventually fail.&lt;/p&gt;

&lt;p&gt;Networks partition.&lt;/p&gt;

&lt;p&gt;Pods restart.&lt;/p&gt;

&lt;p&gt;Message brokers retry.&lt;/p&gt;

&lt;p&gt;Databases become temporarily unavailable.&lt;/p&gt;

&lt;p&gt;Those aren't exceptional situations—they're normal operating conditions.&lt;/p&gt;

&lt;p&gt;The goal isn't to eliminate failures.&lt;/p&gt;

&lt;p&gt;The goal is to design systems where failures don't cost customers money.&lt;/p&gt;

&lt;p&gt;Exactly-once processing remains a useful concept, but in real production systems we achieve something more practical: at-least-once delivery combined with idempotent business logic.&lt;/p&gt;

&lt;p&gt;When those pieces work together—idempotency, retries, the Transactional Outbox pattern, consumer deduplication, Sagas, and strong observability—the result is a payment platform that behaves correctly even when everything around it is unreliable.&lt;/p&gt;

&lt;p&gt;And that's ultimately what users care about.&lt;/p&gt;

&lt;p&gt;They don't care how many times your service retried.&lt;/p&gt;

&lt;p&gt;They only care that they were charged exactly once.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>microservices</category>
      <category>fintech</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
