<?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: Md Mahadi Hasan</title>
    <description>The latest articles on DEV Community by Md Mahadi Hasan (@mahadi_hasan_fa9afbbe0512).</description>
    <link>https://dev.to/mahadi_hasan_fa9afbbe0512</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%2F2332313%2Fbb660d2b-f958-4c1a-b56d-cc869ab4b423.jpg</url>
      <title>DEV Community: Md Mahadi Hasan</title>
      <link>https://dev.to/mahadi_hasan_fa9afbbe0512</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahadi_hasan_fa9afbbe0512"/>
    <language>en</language>
    <item>
      <title>Managing Distributed Transactions in Microservices: 2PC, Saga, Kafka</title>
      <dc:creator>Md Mahadi Hasan</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:45:19 +0000</pubDate>
      <link>https://dev.to/mahadi_hasan_fa9afbbe0512/managing-distributed-transactions-in-microservices-2pc-saga-kafka-25n</link>
      <guid>https://dev.to/mahadi_hasan_fa9afbbe0512/managing-distributed-transactions-in-microservices-2pc-saga-kafka-25n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23rj0html9pxxvhv89pe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23rj0html9pxxvhv89pe.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a normal database transaction is not enough when an order crosses multiple services&lt;/strong&gt;&lt;br&gt;
Imagine that we are building an e-commerce application with three microservices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OrderService creates and manages orders.&lt;/li&gt;
&lt;li&gt;PaymentService charges customers and processes refunds.&lt;/li&gt;
&lt;li&gt;InventoryService reserves and releases products.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, the order flow looks like simple:&lt;/p&gt;

&lt;p&gt;Create the order.&lt;br&gt;
Charge the customer’s card.&lt;br&gt;
Update the inventory.&lt;br&gt;
Confirm the order.&lt;br&gt;
But what happens if the payment succeeds and the product is out of stocks? What happens if the database transaction succeeds but the application crashes the infomation before it publishes an event to Kafka?&lt;/p&gt;

&lt;p&gt;These questions actually have made the distributed transactions become interesting and difficult.&lt;/p&gt;

&lt;p&gt;In this article, I will use the order workflow to explain local database transactions, Two-Phase Commit (2PC), Saga choreography, Saga orchestration, and the Transactional Outbox pattern.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting with a familiar database transaction
&lt;/h2&gt;

&lt;p&gt;If all the operations belong to one application and use the same database, we might write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction = database.begin()
try:
    createOrder()
    chargeCard()
    updateInventory()
    transaction.commit()
except:
    transaction.rollback()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a normal local ACID transaction. Either every database operation is committed, or every operation is rolled back.&lt;/p&gt;

&lt;p&gt;The approach works when all the changes are controlled by the same transaction manager. It stops working whenOrderService, PaymentService, and InventoryService has separate databases system.&lt;/p&gt;

&lt;p&gt;Rolling back the OrderService database cannot automatically:&lt;/p&gt;

&lt;p&gt;Undo a record committed by PaymentService.&lt;br&gt;
Restore inventory changed by InventoryService.&lt;br&gt;
Reverse a charge made through an external payment provider.&lt;br&gt;
This is the first important distinction- wrapping several service calls inside try/catch block does not create a distributed transaction.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Two-Phase Commit works
&lt;/h2&gt;

&lt;p&gt;Two-Phase Commit attempts to make one transaction atomic across multiple participating resources. A transaction coordinator manages the process in two phases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Prepare&lt;/strong&gt;&lt;br&gt;
The coordinator asks every participant whether it is ready to commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coordinator -&amp;gt; Order database: Prepare
Coordinator -&amp;gt; Payment database: Prepare
Coordinator -&amp;gt; Inventory database: Prepare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each participant performs the required work without making the final commit. It normally keeps the relevant resources locked and responds with either “ready” or “failed.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Commit or rollback&lt;/strong&gt;&lt;br&gt;
If every participant is ready, the coordinator instructs all of them to commit. If any participant cannot prepare, the coordinator tells all participants to roll back.&lt;/p&gt;

&lt;p&gt;2PC can provide strong consistency, but it comes with tradeoffs:&lt;/p&gt;

&lt;p&gt;Participants may hold locks while waiting for the coordinator.&lt;br&gt;
A coordinator or network failure can leave transactions blocked.&lt;br&gt;
Services become more tightly coupled at the transaction level.&lt;br&gt;
Every resource must support the distributed transaction protocol.&lt;br&gt;
An external payment gateway will usually not participate in our database transaction.&lt;br&gt;
Because of these limitations, 2PC is often a difficult fit for independently deployed microservices. It may still make sense in controlled environments with compatible resources and a strong consistency requirement, but it should not be our automatic choice.&lt;/p&gt;
&lt;h2&gt;
  
  
  Saga: a sequence of local transactions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1l50jxgs2rcep3bs1sad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1l50jxgs2rcep3bs1sad.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Saga pattern approaches the problem different way. Instead of trying to create one transaction across all services, it breaks the business workflow into multiple local transactions.&lt;/p&gt;

&lt;p&gt;Each service commits its own change. If a later step fails, the system runs compensating actions to logically reverse the work already completed.&lt;/p&gt;

&lt;p&gt;For our order workflow, the transactions and compensations might look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfv4yqdimh7nofrh1jcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfv4yqdimh7nofrh1jcv.png" alt=" " width="710" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A compensation is not the same as a database rollback. A refund is a new business transaction. It does not erase the original charge however it creates another financial operation that reverses its effect.&lt;/p&gt;

&lt;p&gt;This also means a Saga provides eventual consistency. For a short time, one service may show a successful payment while another is still attempting to reserve the inventory.&lt;/p&gt;

&lt;p&gt;There are two common ways to coordinate a Saga- choreography and orchestration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saga choreography with Kafka&lt;/strong&gt;&lt;br&gt;
In choreography, there is no central component controlling the entire workflow. Each service listens for relevant events, performs its local transaction, and publishes the next event.&lt;/p&gt;

&lt;p&gt;The successful flow might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderService
    creates a PENDING order
    publishes OrderPlaced
PaymentService
    consumes OrderPlaced
    charges the customer
    publishes PaymentSucceeded
InventoryService
    consumes PaymentSucceeded
    reserves the products
    publishes InventoryReserved
OrderService
    consumes InventoryReserved
    marks the order as CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kafka topics carry facts about things that have already happened. Event names such as OrderPlaced, PaymentSucceeded, and InventoryReserved make those facts clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  If payment fails, then?
&lt;/h2&gt;

&lt;p&gt;If the card is declined, PaymentService publishes PaymentDeclined. OrderService consumes that event and cancels the order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderPlaced
    -&amp;gt; PaymentDeclined
    -&amp;gt; OrderCancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What if inventory reservation fails?
&lt;/h2&gt;

&lt;p&gt;This case is different. The card has already been charged, so the completed payment must be compensated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentSucceeded
    -&amp;gt; InventoryReservationFailed
    -&amp;gt; RefundRequested
    -&amp;gt; PaymentRefunded
    -&amp;gt; OrderCancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PaymentService consumes RefundRequested, processes the refund, and publishes PaymentRefunded. OrderService can then move the order to its final cancelled state.&lt;/p&gt;

&lt;p&gt;In a real system, we may use more detailed states such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PENDING_PAYMENT
PAYMENT_COMPLETED
AWAITING_INVENTORY
CANCELLATION_PENDING_REFUND
CANCELLED
CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These states make temporary conditions visible and easier to operate safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of choreography
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Services remain loosely coupled.&lt;/li&gt;
&lt;li&gt;There is no central workflow controller.&lt;/li&gt;
&lt;li&gt;Adding an event consumer does not always require modifying the producer.&lt;/li&gt;
&lt;li&gt;The approach fits event-driven systems naturally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges of choreography
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The full workflow becomes difficult to see as the number of events grows.&lt;/li&gt;
&lt;li&gt;Event loops and unexpected dependencies can develop.&lt;/li&gt;
&lt;li&gt;Debugging requires good correlation IDs and distributed tracing.&lt;/li&gt;
&lt;li&gt;Compensation logic may be spread across several services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choreography works well for relatively simple flows. As a business process becomes more complicated, orchestration can make the workflow easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saga orchestration
&lt;/h2&gt;

&lt;p&gt;In orchestration, a dedicated orchestrator controls the sequence. It sends commands to services, receives their results, and decides which step or compensation should happen next.&lt;/p&gt;

&lt;p&gt;A simplified order Saga could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderSagaOrchestrator:
    create pending order
    request payment
    if payment fails:
        cancel order
        stop
    request inventory reservation
    if inventory reservation fails:
        request payment refund
        cancel order
        stop
    confirm order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator does not need to contain the internal business logic for charging a card or reserving a product. Those responsibilities still belong to PaymentService and InventoryService. The orchestrator owns the workflow and its state transitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is a Saga orchestrator the same as a 2PC coordinator?
&lt;/h2&gt;

&lt;p&gt;No. They both coordinate participants, but they provide different guarantees.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farh393v33ocl6nyjd112.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farh393v33ocl6nyjd112.png" alt=" " width="799" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This distinction helped me understand why Saga orchestration is not simply another implementation of 2PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database-and-Kafka consistency problem
&lt;/h2&gt;

&lt;p&gt;Saga introduces another challenge. A service often needs to save data in its database and publish an event to Kafka.&lt;/p&gt;

&lt;p&gt;Consider PaymentService:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Charge the customer.&lt;/li&gt;
&lt;li&gt;Save the successful payment.&lt;/li&gt;
&lt;li&gt;Publish PaymentSucceeded to Kafka.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What happens if the payment is saved, but the application crashes before step three?&lt;/p&gt;

&lt;p&gt;The PaymentService database says the payment succeeded, but InventoryService never receives PaymentSucceeded. The order becomes stuck.&lt;/p&gt;

&lt;p&gt;Changing the order of the operations does not solve the problem. If we publish the event first and the database save then fails, other services will react to an event that does not match PaymentService’s state.&lt;/p&gt;

&lt;p&gt;Kafka and the service database normally cannot share one local database transaction. This is the dual-write problem.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Simple State Management in React(TypeScript) with Zustand</title>
      <dc:creator>Md Mahadi Hasan</dc:creator>
      <pubDate>Wed, 16 Jul 2025 20:46:35 +0000</pubDate>
      <link>https://dev.to/mahadi_hasan_fa9afbbe0512/simple-state-management-in-reacttypescript-with-zustand-4bcp</link>
      <guid>https://dev.to/mahadi_hasan_fa9afbbe0512/simple-state-management-in-reacttypescript-with-zustand-4bcp</guid>
      <description>&lt;p&gt;Zustand is a fast, scalable, and minimal state management library for React. It’s simpler than Redux and works well with small to medium-sized applications. It provides a clean and straightforward API also does not requires any boilerplate to implememt Zustand. It can be used both inside and outside of React components.&lt;/p&gt;

&lt;p&gt;There are some advantage on Zustand over redux.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimal API with no boilerplate&lt;/li&gt;
&lt;li&gt;Global store without the need for React Context&lt;/li&gt;
&lt;li&gt;First-class TypeScript support&lt;/li&gt;
&lt;li&gt;Middleware support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step: Using Zustand in a React App
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Install Zustand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install Zustand with npm or yarn:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install zustand&lt;/code&gt;&lt;br&gt;
or &lt;br&gt;
&lt;code&gt;yarn add zustand&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create a Typed Store&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s create a simple counter store with TypeScript types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/store/useCounterStore.ts
import { create } from 'zustand';

interface CounterState {
  count: number;
  increase: () =&amp;gt; void;
  decrease: () =&amp;gt; void;
  reset: () =&amp;gt; void;
}

const useCounterStore = create&amp;lt;CounterState&amp;gt;((set) =&amp;gt; ({
  count: 0,
  increase: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count + 1 })),
  decrease: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count - 1 })),
  reset: () =&amp;gt; set({ count: 0 }),
}));

export default useCounterStore;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Use the Store in a Component&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s use this store inside a React component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/Counter.tsx
import React from 'react';
import useCounterStore from '../store/useCounterStore';

const Counter: React.FC = () =&amp;gt; {
  const { count, increase, decrease, reset } = useCounterStore();

  return (
    &amp;lt;div style={{ textAlign: 'center' }}&amp;gt;
      &amp;lt;h2&amp;gt;Count: {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={increase}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={decrease}&amp;gt;-&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={reset}&amp;gt;Reset&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Add the Component to Your App&lt;/strong&gt;&lt;br&gt;
Use the counter component in your main App.tsx file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/App.tsx
import React from 'react';
import Counter from './components/Counter';

const App: React.FC = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Zustand + TypeScript Example&amp;lt;/h1&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist State with Middleware&lt;br&gt;
You can persist store values to localStorage using zustand/middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/store/useAuthStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  token: string | null;
  setToken: (token: string) =&amp;gt; void;
  logout: () =&amp;gt; void;
}

const useAuthStore = create&amp;lt;AuthState&amp;gt;()(
  persist(
    (set) =&amp;gt; ({
      token: null,
      setToken: (token) =&amp;gt; set({ token }),
      logout: () =&amp;gt; set({ token: null }),
    }),
    {
      name: 'auth-storage', // key in localStorage
    }
  )
);

export default useAuthStore;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zustand is a powerful and minimal solution for managing global state in React till now. With the support of TypeScript it likes out of the box and it’s ideal for modern applications where simplicity, scalability, and strong typing are needed.&lt;/p&gt;

</description>
      <category>react</category>
      <category>zustand</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
