<?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: Omkar Dusane</title>
    <description>The latest articles on DEV Community by Omkar Dusane (@omkard).</description>
    <link>https://dev.to/omkard</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%2F4147376%2F74774dea-6a7e-4f40-bedb-9fef3d53551f.jpg</url>
      <title>DEV Community: Omkar Dusane</title>
      <link>https://dev.to/omkard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omkard"/>
    <language>en</language>
    <item>
      <title>Durable Workflows Hiding in Plain Sight</title>
      <dc:creator>Omkar Dusane</dc:creator>
      <pubDate>Fri, 02 Oct 2026 20:35:17 +0000</pubDate>
      <link>https://dev.to/omkard/durable-workflows-hiding-in-plain-sight-2889</link>
      <guid>https://dev.to/omkard/durable-workflows-hiding-in-plain-sight-2889</guid>
      <description>&lt;p&gt;In my last post, I walked through one problem where durable execution fit naturally. This time I want to zoom out. Some of the best candidates for this pattern aren't exotic. They're sitting in products we use every day, and most teams build them the traditional way without a second thought.&lt;/p&gt;

&lt;p&gt;One note: these are my own sketches of how such systems could be modeled, not how any of these companies actually build them. The code is pseudocode, not production code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern We Keep Missing
&lt;/h2&gt;

&lt;p&gt;When we build a feature, we usually follow the flow of what the system already has: a Kafka topic here, a status column there, a cron job somewhere else. Over time, &lt;strong&gt;code for orchestration and business code get fused together&lt;/strong&gt;. Retries, acknowledgements, timers and state sync end up tangled with the actual product rules.&lt;/p&gt;

&lt;p&gt;A fresh look at these use cases lets us spot the workflow shape early, in the initial version of a product. Then the separation isn't only about infrastructure. Whole behavioral patterns can be pulled out of the backend and treated as one unit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 1: The Booking That Lives for Weeks (Airbnb-style)
&lt;/h2&gt;

&lt;p&gt;A traveler confirms a booking, and from that moment a reservation state machine starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payment:&lt;/strong&gt; the account is debited on a set day after confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refund window:&lt;/strong&gt; fully refundable until 48 hours before check-in. After that, cancellation is still possible, but only for the remaining nights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy varies per listing&lt;/strong&gt;, so the rules aren't global.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human in the loop:&lt;/strong&gt; a cancellation on the day of arrival needs a decision from the host or traveler. We want humans involved only for that decision, nothing else.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reservationWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;onSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cancelRequested&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleepUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debitDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleepUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkIn&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// free-cancel window closes&lt;/span&gt;
  &lt;span class="c1"&gt;// ...partial refund rules apply from here&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isArrivalDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hostDecision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// human step&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;applyResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timeline of the booking is the code. Humans only show up where judgment is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 2: The Order and Its Journey (E-commerce)
&lt;/h2&gt;

&lt;p&gt;This one is everywhere. A delivery partner like DHL sends webhooks about a package's status. The usual approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The webhook lands on a Kafka topic.&lt;/li&gt;
&lt;li&gt;One service picks it up, processes it, and produces a follow-up event.&lt;/li&gt;
&lt;li&gt;Another service picks that up, decides whether this user even wants this notification ("out for delivery" yes, "arrived at intermediate hub" probably not), and sends it.&lt;/li&gt;
&lt;li&gt;Retries, acknowledgement checks and dedupe are scattered across all of it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Business rules and orchestration end up in the same handlers. Changing "who gets which notification" means touching retry logic too.&lt;/p&gt;

&lt;p&gt;Now flip the view. &lt;strong&gt;The order is an entity, and there's one long-running workflow on it&lt;/strong&gt;, from the seller's warehouse to the customer's door:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;orderJourneyWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;onSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;carrierUpdate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifyUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delivered&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the handlers live in one place, and they read like plain API calls instead of "Kafka subscribe, Kafka publish, blah blah."&lt;/p&gt;

&lt;h2&gt;
  
  
  looks like a React Component?
&lt;/h2&gt;

&lt;p&gt;This is the mental model that clicked for me. A React component lives for maybe two minutes while a user interacts with it. It holds state, has handlers for events, and each handler says how state changes.&lt;/p&gt;

&lt;p&gt;A durable workflow is the same shape, but &lt;strong&gt;the "session" stretches over days or weeks&lt;/strong&gt;, and the "clicks" come from carriers, hosts, timers or other services. The business logic is only this: given the current state and this interaction, what's the new state? Persisting state, queuing events and waking up at the right time are abstracted away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Shared Work, Done Once
&lt;/h2&gt;

&lt;p&gt;Not every workflow is per-user or per-order. Take a streaming home screen: "Top 10 in your region" depends on the &lt;strong&gt;segment&lt;/strong&gt; a user belongs to, not the user. There are far fewer segments than users, so for each segment and each interval you want to compute once and serve thousands. A small workflow per segment (gather, rank, publish, sleep until the next interval) keeps that calculation from running thousands of times, with durable state if it fails halfway. Fair warning: the heavy compute lives elsewhere, so the win here is orchestration, not the ranking itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does It Have to Be Temporal?
&lt;/h2&gt;

&lt;p&gt;No. You could build this yourself with a persistence layer for state and a queue for events. Temporal is one way, and there are others emerging. The tool matters less than the realization: &lt;strong&gt;software keeps getting more complex, and we keep working on smaller pieces of bigger systems.&lt;/strong&gt; That's a good moment to pull complete packages of functionality into one place where they're maintainable, and to solve their shared problem (orchestration) once, together. Consistency is a bonus.&lt;/p&gt;

&lt;p&gt;Cheers!!&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>distributedsystems</category>
      <category>durableexecution</category>
    </item>
    <item>
      <title>Demystifying Durable Workflows: A use case from Uber</title>
      <dc:creator>Omkar Dusane</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:39:31 +0000</pubDate>
      <link>https://dev.to/omkard/demystifying-durable-workflows-a-use-case-from-uber-5077</link>
      <guid>https://dev.to/omkard/demystifying-durable-workflows-a-use-case-from-uber-5077</guid>
      <description>&lt;p&gt;In my last post, I introduced Durable Execution (or "Code-First Workflows") as a fast-emerging architectural primitive. A paradigm only really clicks when you watch it solve a messy, real problem, so let's look at one.&lt;/p&gt;

&lt;p&gt;Back in 2018-19, a friend at Uber walked me through how they handled ride-end payment notifications in India. It ran on Cadence, the engine that Temporal later grew out of, built by the same people. Below is the problem, and how I'd model it with Temporal today.&lt;/p&gt;

&lt;p&gt;One note: this is my understanding from that conversation, and the code is a simplified sketch, not production code from Uber.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Product Challenge: Cash vs. Digital
&lt;/h2&gt;

&lt;p&gt;Cash was still heavily used for ridesharing in India back then, and it created real friction at the end of a trip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The double-pay confusion.&lt;/strong&gt; A rider picks Digital (UPI, credit card, Paytm), but the driver asks for cash anyway, out of habit or confusion. The app needed to say it plainly: &lt;em&gt;"Digital payment selected. Do NOT pay cash to the driver."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The default payment trap.&lt;/strong&gt; Riders carried over a last-used method, often Cash, and only realized near the end of the trip that they had no cash on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No post-ride switching.&lt;/strong&gt; Changing the payment method after the ride ended hadn't shipped yet, so a trip that ended on Cash left rider and driver in an awkward standoff.
## The Requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give riders one last window to switch payment methods before the ride locked, product wanted a notification with three rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timing:&lt;/strong&gt; send it 5 minutes before the ride ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap:&lt;/strong&gt; at most 3 notifications per ride.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moving target:&lt;/strong&gt; the ETA keeps changing with traffic, so "5 minutes before the end" is never a fixed timestamp.
Sending a notification is trivial. Sending it at the right moment, the right number of times, on a trip whose end time keeps shifting, is the actual problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option A: Build It Yourself
&lt;/h2&gt;

&lt;p&gt;With traditional building blocks (DB status flags, SQS delay queues, Redis TTL keys, cron scripts), this is possible, but painful for what it delivers. You end up writing a bespoke distributed state machine just to manage time-based logic. The glue code you own looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timer engine.&lt;/strong&gt; Thousands of ephemeral delayed jobs in SQS or RabbitMQ, or a polling mechanism, just to track "5 minutes before the end" while ETAs keep shifting with traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locking and state sync.&lt;/strong&gt; Guarding against double notifications when an ETA update and a timer expiry hit two different workers at once, and keeping a Redis counter for the 3-notification cap in sync with the ride lifecycle across services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup.&lt;/strong&gt; Purging or skipping scheduled messages when a ride cancels, ends abruptly, or completes early.
Most of your code ends up being plumbing (retries, locks, timer offsets, state cleanup) rather than the product logic of sending one notification. You've built a fragile, miniature workflow engine inside your own application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option B: Durable Execution
&lt;/h2&gt;

&lt;p&gt;With a code-first workflow engine like Temporal, you don't build a state machine out of tables and timers. Your code IS the state machine.&lt;/p&gt;

&lt;p&gt;Temporal gives you durable timers and signals. You write a plain loop with a sleep in it, and signals feed in ETA updates and ride-end events. If the worker running it dies, the cluster restores the exact loop state, timer, and variables and carries on as if nothing happened.&lt;/p&gt;

&lt;p&gt;Here's a simplified sketch of how this could look in Temporal (pseudocode, not production code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rideEndNotificationWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rideId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getInitialEta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rideId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rideActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;onSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;etaUpdated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newEta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newEta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;onSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rideEnded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rideActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// ended or cancelled&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rideActive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// durable timer: wakes early if a signal arrives&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleepOrSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// re-check: ETA may have changed, or the ride may be over&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendPaymentReminder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rideId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sent&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleepOrSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// brief pause before any re-send&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it top to bottom and it's just the business rule: wait until 5 minutes before the end, notify, stop after 3 or when the ride is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Wins
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No external state.&lt;/strong&gt; No Redis counter, no delay queues, no status flags. The counter and the timer live in the workflow itself, so there's nothing to keep in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation and cleanup come almost free.&lt;/strong&gt; When the ride ends or cancels, the signal flips a flag and the loop exits. There are no orphaned delayed messages to hunt down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The code reads like the business rule.&lt;/strong&gt; Product can look at that loop and recognize their own spec. That's a big deal for maintenance.
None of this is free, and I'll cover where it bites in the next post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next one, I'll get into why some engineering organizations evaluated Temporal and walked away, and how those evaluations played out.&lt;/p&gt;

&lt;p&gt;Cheers!!&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>distributedsystems</category>
      <category>durableexecution</category>
    </item>
    <item>
      <title>Spotting an architectural paradigm Pioneered by Temporal - Durable workflows</title>
      <dc:creator>Omkar Dusane</dc:creator>
      <pubDate>Mon, 28 Sep 2026 15:03:26 +0000</pubDate>
      <link>https://dev.to/omkard/spotted-something-vercel-workflows-yet-one-more-variant-of-an-emerging-architectural-paradigm-1pne</link>
      <guid>https://dev.to/omkard/spotted-something-vercel-workflows-yet-one-more-variant-of-an-emerging-architectural-paradigm-1pne</guid>
      <description>&lt;p&gt;I have been noticing something manifesting a massive architectural emergence in software engineering called Durable Execution (or "Code-First Workflows"). It started gaining traction in 2018-19 when I was looking for a declarative workflow writing framework and landed on Apache Cadence, by Maxim Fateev being used at Uber. Talked to Maxim back then and realized I would have to build a full Node.js SDK from scratch to use it, so I took a different path, but kept checking on it. When Uber split Cadence into open-source (similar to how Kafka detached from LinkedIn), I was fascinated by the architecture and kept finding new use cases. By 2022, I was evaluating and adopting Temporal for real production-scale projects, and I personally enjoyed modeling payment and invoicing workflows with it. Recently through 2024 to early 2026, I have been seeing another major use case emerge around evaluation loops and agentic execution. Modeling long-running tasks into discrete pieces remains a fascinating challenge, and how you approach that modeling is the key differentiator between shooting yourself in the foot or seeing immense simplification and benefits.&lt;/p&gt;

&lt;p&gt;I will cover why certain engineering organizations rejected Temporal and how those evaluations played out in a separate post. For now, the core takeaway is clear: durable execution has officially crossed over from specialized infrastructure into a standard developer primitive.&lt;/p&gt;

&lt;p&gt;This evolution reminds me a lot of how Apache Kafka transformed data pipelines. Just as Kafka became the undisputed open-source standard for event streaming after leaving LinkedIn, I am watching closely to see if Temporal achieves that similar ubiquitous, foundational fate for orchestration. Modern serverless platforms like Inngest are pushing this paradigm even further into everyday web applications, but at its core, the heavy lifting of durable state management traces right back to this fundamental engine.&lt;/p&gt;

&lt;p&gt;Whether Temporal becomes the industry-wide default or remains part of a broader ecosystem alongside lighter abstractions, durable execution is here to stay. In upcoming posts, I will dive into real-world production use cases and practical workflow modeling from architecting payment engines to designing resilient AI evaluation loops.&lt;/p&gt;

&lt;p&gt;Cheers!!&lt;/p&gt;

</description>
      <category>temporal</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
