<?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: Rahman Nugar</title>
    <description>The latest articles on DEV Community by Rahman Nugar (@rahmannugar).</description>
    <link>https://dev.to/rahmannugar</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%2F3408076%2F1bb7d54a-d698-4106-ba89-095779ea601a.JPG</url>
      <title>DEV Community: Rahman Nugar</title>
      <link>https://dev.to/rahmannugar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahmannugar"/>
    <language>en</language>
    <item>
      <title>Designing a Multi-Tenant Storefront With Wildcard Subdomains</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 18:39:59 +0000</pubDate>
      <link>https://dev.to/rahmannugar/designing-a-multi-tenant-storefront-with-wildcard-subdomains-4047</link>
      <guid>https://dev.to/rahmannugar/designing-a-multi-tenant-storefront-with-wildcard-subdomains-4047</guid>
      <description>&lt;p&gt;At my workplace, I worked on an ERP platform used by fashion businesses to manage customers, body measurements, products, orders, invoices, inventory, staff, and other day-to-day operations. Each business also had a public storefront where customers could browse products and check out.&lt;/p&gt;

&lt;p&gt;The storefront started as a simple sharing feature. Businesses could publish products, copy a link, and send it to customers outside the main workspace. That worked well because the storefront was mostly a product catalogue, and most of the sales process still happened after the customer contacted the business.&lt;/p&gt;

&lt;p&gt;As the platform evolved, the storefront became much more than a catalogue. Customers were discovering businesses through shared links, browsing products, placing an order, and tracking orders directly from the storefront. That introduced new technical requirements around branded storefronts, SEO, server-rendered metadata, public checkout, pricing, and analytics.&lt;/p&gt;

&lt;p&gt;This article explores how I designed the storefront around wildcard subdomains, immutable shop identities, server-side shop resolution, and a scalable analytics pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Giving the Storefront Its Own Identity&lt;/li&gt;
&lt;li&gt;Business Names, Reserved Names, and Subdomains&lt;/li&gt;
&lt;li&gt;Resolving a Storefront&lt;/li&gt;
&lt;li&gt;Active and Inactive Storefronts&lt;/li&gt;
&lt;li&gt;Location and Currency&lt;/li&gt;
&lt;li&gt;Product Pages and Share Previews&lt;/li&gt;
&lt;li&gt;Storefront Event Ingestion&lt;/li&gt;
&lt;li&gt;Processing Raw Events&lt;/li&gt;
&lt;li&gt;Counting Unique Visitors With HyperLogLog&lt;/li&gt;
&lt;li&gt;Domain Routing and Local DNS&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Giving the Storefront Its Own Identity
&lt;/h2&gt;

&lt;p&gt;The original storefront was fairly simple. It was a React application that fetched a business and rendered its products. Beyond that, there wasn't much to it. There were no branded storefronts, analytics, subdomains, or even a separate identity beyond the business itself.&lt;/p&gt;

&lt;p&gt;Supporting those capabilities meant the storefront needed its own data model. I introduced a dedicated &lt;code&gt;shop&lt;/code&gt; entity to represent the public storefront.&lt;/p&gt;

&lt;p&gt;The business remained the source of operational data such as customers, products, orders, subscriptions, staff, and settings. The &lt;code&gt;shop&lt;/code&gt; entity became the source of everything related to the public-facing storefront.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shops
  id
  businessId
  slug
  subdomain
  status
  inactiveReason
  createdAt
  updatedAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;businessId&lt;/code&gt;, &lt;code&gt;slug&lt;/code&gt;, and &lt;code&gt;subdomain&lt;/code&gt; are all unique. This gives every storefront a stable internal identity, a human-readable public address, and an immutable fallback slug that continues to identify the storefront even if the business name or subdomain changes.&lt;/p&gt;

&lt;p&gt;To support existing businesses, I backfilled the new table by generating an immutable slug and deriving a subdomain from each business name. If a derived subdomain was reserved or already in use, the migration failed instead of generating an alternative automatically. Since the business name determines the public storefront address, changing it behind the user's back would make the storefront feel unpredictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Business Names, Reserved Names, and Subdomains
&lt;/h2&gt;

&lt;p&gt;Every storefront has two public addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abdulrahmon.cuttoshape.com
abdulrahmon.cuttoshape.com/products/:productId

cuttoshape.com/shop/:slug
cuttoshape.com/shop/:slug/products/:productId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subdomain is the primary storefront address that customers can easily remember and share. The slug is a permanent fallback.&lt;/p&gt;

&lt;p&gt;Rather than asking businesses to choose a separate storefront name, the subdomain is derived from the business name. When a business is created or renamed, the name is normalized by converting it to lowercase, removing unsupported characters, replacing spaces with hyphens, collapsing repeated separators, and ensuring it fits within DNS label limits.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adenuga Marts -&amp;gt; adenuga-marts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the business name determines the public storefront address, the same request also validates the platform's subdomain policy. Derived subdomains cannot conflict with existing storefronts or use reserved platform names such as &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;www&lt;/code&gt;, &lt;code&gt;docs&lt;/code&gt;, or other names the platform may need in the future.&lt;/p&gt;

&lt;p&gt;If validation fails, the business name update is rejected. The system does not generate alternatives such as &lt;code&gt;adenuga-marts-2&lt;/code&gt; because silently changing a business's public identity would make storefront URLs unpredictable.&lt;/p&gt;

&lt;p&gt;The fallback slug serves a different purpose. It is generated by the backend, immutable, and never exposed for editing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cuttoshape.com/shop/7Kxq9BvL2pQ8mNz4RcT6yWaDk9Lp2Qs8Vn3Tx5Zr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It isn't meant to be memorable but to permanently identify the storefront, even if the business name changes or the custom subdomain becomes unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Resolving a Storefront
&lt;/h2&gt;

&lt;p&gt;Every public storefront request begins by resolving the incoming address to a storefront.&lt;/p&gt;

&lt;p&gt;For subdomain requests, the storefront application extracts the tenant label from the request host and asks the backend to resolve it.&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;GET /api/public/storefronts/resolve?subdomain=adenuga-marts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For fallback URLs, the backend resolves the storefront by its immutable slug.&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;GET /api/public/storefronts/slug/:slug
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once resolved, every public operation works with the storefront's internal identifier. Products, checkout, and analytics no longer care whether the request came from a subdomain or a slug.&lt;/p&gt;

&lt;p&gt;The backend also returns the storefront's canonical public URL. If the storefront's subdomain is active, the canonical URL is the subdomain. Otherwise, it is the slug-based URL. Clients simply use the returned value when generating links instead of rebuilding URLs from business names or environment-specific rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Active and Inactive Storefronts
&lt;/h2&gt;

&lt;p&gt;A storefront that doesn't exist isn't the same as one that's unavailable, so I kept the model simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status: active | inactive
inactiveReason: string | null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the storefront doesn't exist, the resolver returns a normal 404.&lt;/p&gt;

&lt;p&gt;If it exists but is inactive, the storefront still resolves, but browsing and checkout are blocked. This lets the frontend show a proper unavailable page instead of treating it as a missing storefront.&lt;/p&gt;

&lt;p&gt;The inactive reason is just a string. That makes it easy to introduce new reasons without updating every client.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Location and Currency
&lt;/h2&gt;

&lt;p&gt;The storefront serves customers from different countries, so it couldn't display the same currency to everyone.&lt;/p&gt;

&lt;p&gt;The product supported three markets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nigeria         -&amp;gt; NGN
United Kingdom  -&amp;gt; GBP
Everywhere else -&amp;gt; USD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I resolved the visitor's market from their request IP and cached the result for one day to reduce requests to the geolocation provider. If the location couldn't be determined, the storefront used the currency specified in the URL. If that wasn't present either, it fell back to the business's primary currency.&lt;/p&gt;

&lt;p&gt;The resolved currency was then used throughout the storefront. Products already supported multiple currencies, including fabric-specific pricing, so customers saw prices that matched their market without requiring separate storefronts.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Product Pages and Share Previews
&lt;/h2&gt;

&lt;p&gt;The original storefront lived inside the multi-tenant React dashboard. That worked well until product pages needed server-generated metadata for SEO and link previews.&lt;/p&gt;

&lt;p&gt;The first approach was a server-rendered endpoint that generated the metadata and redirected customers back to the React storefront. It solved the metadata problem, but the public storefront URL still wasn't responsible for rendering the page.&lt;/p&gt;

&lt;p&gt;I eventually split the public storefront from the dashboard into its own Next.js application. Storefronts and product pages now resolve their data, generate metadata, and render HTML from the same URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Storefront Event Ingestion
&lt;/h2&gt;

&lt;p&gt;As the storefront grew, both businesses and the platform needed visibility into how storefronts were performing.&lt;/p&gt;

&lt;p&gt;The storefront emits a small set of analytics events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shop_view
product_view
product_search
add_to_cart
checkout_started
checkout_submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Analytics events are submitted asynchronously. If event collection fails, customers can still browse products, add items to their cart, and check out normally.&lt;/p&gt;

&lt;p&gt;Not every event is captured the same way. Impression events such as &lt;code&gt;shop_view&lt;/code&gt; and &lt;code&gt;product_view&lt;/code&gt; are sent after a page has rendered, while interaction events such as &lt;code&gt;add_to_cart&lt;/code&gt;, &lt;code&gt;checkout_started&lt;/code&gt;, and &lt;code&gt;checkout_submitted&lt;/code&gt; are sent directly from user actions. Search events are only recorded after the search has settled rather than on every keystroke.&lt;/p&gt;

&lt;p&gt;Each event contains enough information for downstream processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shopId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shop_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"businessId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"business_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product_view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visitorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"visitor_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session_xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientEventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"event_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-11T14:20:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;clientEventId&lt;/code&gt; uniquely identifies an analytics event. The backend enforces a unique constraint on &lt;code&gt;(shopId, clientEventId)&lt;/code&gt;, making retries safe without counting the same event twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Processing Raw Events
&lt;/h2&gt;

&lt;p&gt;The backend doesn't update analytics counters directly from the public request.&lt;/p&gt;

&lt;p&gt;Instead, every accepted event is stored as a raw event and queued for background processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Return 202
                      ▲
                      │
Storefront ──► Store raw event
                      │
                      ▼
                   Queue
                      │
                      ▼
              Analytics worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A raw event represents a single analytics event before aggregation.&lt;br&gt;
&lt;/p&gt;

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

shopId
businessId
productId
eventType
visitorId
sessionId
clientEventId
currency
clientTimestamp
processedAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an event is first stored, &lt;code&gt;processedAt&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;. After the worker successfully processes it, &lt;code&gt;processedAt&lt;/code&gt; is updated. This allows the worker to safely retry failed jobs without processing the same event twice.&lt;/p&gt;

&lt;p&gt;The worker aggregates raw events into daily analytics tables for storefronts, products, and searches. These rollups power the analytics dashboard, while the raw events are retained for 90 days before a scheduled cleanup job removes processed records.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Counting Unique Visitors With HyperLogLog
&lt;/h2&gt;

&lt;p&gt;Page views alone weren't enough because businesses also wanted to know how many unique visitors were reaching their storefronts.&lt;/p&gt;

&lt;p&gt;Calculating distinct visitors from the raw events becomes more expensive as traffic grows because every visitor identifier has to be stored and queried.&lt;/p&gt;

&lt;p&gt;I used Redis HyperLogLog for this part of the pipeline. Rather than storing every visitor identifier, HyperLogLog hashes each identifier and keeps only enough information to estimate how many unique values have been seen. The estimate isn't exact, but memory usage remains almost constant regardless of whether a storefront receives hundreds or millions of visitors.&lt;/p&gt;

&lt;p&gt;As the analytics worker processes each event, it identifies the visitor using the persistent &lt;code&gt;visitorId&lt;/code&gt;, falling back to the &lt;code&gt;sessionId&lt;/code&gt; or request IP when necessary, and updates the daily HyperLogLog key for that storefront. Retrying the same event is safe because adding the same identifier again doesn't change the estimate.&lt;/p&gt;

&lt;p&gt;The dashboard combines exact event totals from the SQL rollup tables with estimated unique visitor counts from HyperLogLog. Daily HyperLogLog keys can also be merged, allowing Redis to estimate distinct visitors across a selected period without counting returning visitors more than once.&lt;/p&gt;

&lt;p&gt;Storefront analytics is intentionally limited to predefined reporting periods of up to one year. HyperLogLog keys are retained for 400 days, giving one-year reports a small retention buffer while preventing Redis memory from growing without bounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Domain Routing and Local DNS
&lt;/h2&gt;

&lt;p&gt;The production setup uses a wildcard DNS record so any valid business subdomain resolves to the public storefront application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.cuttoshape.com
        ↓
Public storefront app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application extracts the subdomain from the request host, resolves the corresponding storefront, and renders the page.&lt;/p&gt;

&lt;p&gt;Slug routes follow a different path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cuttoshape.com/shop/:slug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of extracting a subdomain, the application resolves the storefront directly from its immutable slug before rendering the page.&lt;/p&gt;

&lt;p&gt;I also wanted local development to behave like production, so I used &lt;code&gt;dnsmasq&lt;/code&gt; to configure wildcard local DNS instead of relying on &lt;code&gt;localhost&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address=/cuttoshape.local/127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That allowed me to test requests 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;adenuga.cuttoshape.local
anything.cuttoshape.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;locally before deploying the wildcard DNS configuration.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>eventdriven</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>How Applications Stay Available During Deployment</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Fri, 26 Jun 2026 12:30:41 +0000</pubDate>
      <link>https://dev.to/rahmannugar/how-applications-stay-available-during-deployment-44he</link>
      <guid>https://dev.to/rahmannugar/how-applications-stay-available-during-deployment-44he</guid>
      <description>&lt;p&gt;You just built your application and you're preparing to deploy it. Then you ask the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens to my application and its clients and services when I make changes and deploy?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're building a side project with only a few users, it's easy to simply redeploy and not bother too much about what happens during deployment.&lt;/p&gt;

&lt;p&gt;However, as your application grows, deployment itself becomes an important part of software engineering. Clients and services continue interacting with your application while you're replacing it with a newer version, so how do modern applications continue serving requests without going offline?&lt;/p&gt;

&lt;p&gt;There are four common deployment strategies, each approaching this problem differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Restart / Recreate&lt;/li&gt;
&lt;li&gt;Rolling Deployment&lt;/li&gt;
&lt;li&gt;Canary Deployment&lt;/li&gt;
&lt;li&gt;Blue-Green Deployment&lt;/li&gt;
&lt;li&gt;Choosing the Right Deployment Strategy&lt;/li&gt;
&lt;li&gt;Deployment Isn't Just About Code&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Restart / Recreate
&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%2Fumyjva5qwoxrgiii8p6k.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%2Fumyjva5qwoxrgiii8p6k.png" alt="Restart" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Restart (also known as Recreate) is the simplest deployment strategy.&lt;/p&gt;

&lt;p&gt;In this method, deploying a new version of your application simply turns off the current version and starts the newer one. It's simple, easy to understand, and requires very little infrastructure.&lt;/p&gt;

&lt;p&gt;The downside is that it introduces a temporary blackout. While the old version has stopped and the newer version is still starting, clients and services are unable to communicate with your application.&lt;/p&gt;

&lt;p&gt;Rollback also isn't immediate. If something goes wrong after deployment, you need to redeploy the previous version, introducing another period of downtime.&lt;/p&gt;

&lt;p&gt;This deployment strategy is rarely used for customer-facing production applications because of the downtime it introduces, but it's perfectly reasonable for personal projects, internal tools, or applications where a brief outage is acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Rolling Deployment
&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%2Fsxbx4kkwnmjvmu1vyr9m.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%2Fsxbx4kkwnmjvmu1vyr9m.png" alt="Rolling" width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
Rolling deployment approaches the problem differently.&lt;/p&gt;

&lt;p&gt;Instead of replacing every running instance at once, you gradually replace instances of the older version with newer ones. As each new instance starts, health checks validate that it is healthy before traffic is routed to it. This process continues until every running instance has been replaced.&lt;/p&gt;

&lt;p&gt;Unlike the restart strategy, clients and services can continue communicating with your application throughout the deployment. Rolling deployment is also the default deployment strategy in Kubernetes (K8s).&lt;/p&gt;

&lt;p&gt;One important caveat is that rolling deployments generally require two or more running instances of your application. If you only have a single instance, there is nowhere for traffic to go while that instance is being replaced, making it effectively a restart deployment.&lt;/p&gt;

&lt;p&gt;Rolling deployments also introduce a new challenge.&lt;/p&gt;

&lt;p&gt;During deployment, two versions of your application coexist. Requests are routed to different instances through a load balancer, meaning clients and services may communicate with either the older or newer version at the same time.&lt;/p&gt;

&lt;p&gt;This means both versions must remain compatible throughout the deployment. If the newer version no longer understands requests, shared data, or contracts produced by the older version, parts of the system can begin failing even though the deployment itself is progressing successfully.&lt;/p&gt;

&lt;p&gt;Rolling deployments also make rollbacks more involved. Since the older version is being gradually replaced, a rollback usually means performing another rolling deployment back to the previous version rather than instantly switching traffic to a fully preserved environment.&lt;/p&gt;

&lt;p&gt;Rolling deployment is one of the most widely used deployment strategies because it provides zero downtime without requiring two complete environments. The tradeoff is that both versions of your application temporarily coexist, making backward compatibility an important part of the deployment process.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Canary Deployment
&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%2Fnwj1qoznq32tublroqfy.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%2Fnwj1qoznq32tublroqfy.png" alt="Canary" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Canary deployment approaches the problem differently.&lt;/p&gt;

&lt;p&gt;Instead of gradually replacing every running instance equally, it changes how traffic is routed during deployment.&lt;/p&gt;

&lt;p&gt;Only a small percentage of clients and services are routed to the newer version while the majority continue communicating with the older version.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;95% → Older version&lt;/li&gt;
&lt;li&gt;5% → Newer version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The newer version is continuously monitored using health checks and production metrics. If everything looks healthy, more traffic is gradually shifted to the newer version until it eventually serves all requests.&lt;/p&gt;

&lt;p&gt;The idea is to limit the number of clients and services exposed to a new version until its stability and correctness have been validated.&lt;/p&gt;

&lt;p&gt;Like rolling deployments, both versions of the application coexist during deployment. This means the same compatibility concerns still apply. If the newer version no longer understands requests, shared data, or contracts expected by the older version, parts of the system can begin failing even though only a small percentage of traffic has been routed to it.&lt;/p&gt;

&lt;p&gt;One advantage of canary deployment is that if a problem is detected early, traffic can simply stop being routed to the newer version while the issue is investigated. Since only a small percentage of clients and services were exposed to the new version, the impact of a bad release is significantly reduced.&lt;/p&gt;

&lt;p&gt;Canary deployments are commonly used when reducing the impact of a bad release is more important than deploying every client to the newer version immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Blue-Green Deployment
&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%2Fa5uddvbcs5wes6gg9tdi.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%2Fa5uddvbcs5wes6gg9tdi.png" alt="Blue-Green" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Blue-Green deployment approaches the problem differently.&lt;/p&gt;

&lt;p&gt;Instead of gradually replacing instances or gradually shifting traffic, an entirely new environment is deployed alongside the current one.&lt;/p&gt;

&lt;p&gt;Initially, all traffic continues flowing to the Blue environment while the Green environment is deployed and validated using health checks.&lt;/p&gt;

&lt;p&gt;Once the Green environment has been validated, traffic is switched from Blue to Green.&lt;/p&gt;

&lt;p&gt;This gives us zero downtime throughout the deployment process. It also provides one of the simplest rollback strategies. If something goes wrong after deployment, traffic can simply be routed back to the Blue environment while the issue is investigated.&lt;/p&gt;

&lt;p&gt;The tradeoff is infrastructure cost. Since both environments are running at the same time, you're effectively running two versions of your application during deployment.&lt;/p&gt;

&lt;p&gt;Blue-Green deployment is commonly used for applications where zero downtime and rapid rollback are important requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Choosing the Right Deployment Strategy
&lt;/h2&gt;

&lt;p&gt;There isn't a perfect deployment strategy. Each one comes with its own tradeoffs.&lt;/p&gt;

&lt;p&gt;Restart deployment optimizes for simplicity.&lt;/p&gt;

&lt;p&gt;Rolling deployment optimizes for resource efficiency while allowing clients and services to continue communicating with the application during deployment.&lt;/p&gt;

&lt;p&gt;Canary deployment optimizes for reducing the impact of bad releases by limiting the number of clients and services exposed to newer versions.&lt;/p&gt;

&lt;p&gt;Blue-Green deployment optimizes for zero downtime and provides one of the simplest rollback strategies at the cost of running two environments during deployment.&lt;/p&gt;

&lt;p&gt;Personally, I tend to use Restart deployments for personal projects and Rolling or Canary deployments for applications with active clients and services. The right deployment strategy ultimately depends on the availability requirements, risk tolerance, and infrastructure of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Deployment Isn't Just About Code
&lt;/h2&gt;

&lt;p&gt;One thing that's easy to overlook is that deployments don't only change application code. They often change data too.&lt;/p&gt;

&lt;p&gt;Rolling back code is usually straightforward. Rolling back database changes can be significantly harder, and in some cases, nearly impossible.&lt;/p&gt;

&lt;p&gt;Imagine you're deploying a new version of your application that replaces a &lt;code&gt;full_name&lt;/code&gt; column with a &lt;code&gt;name&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;If the older column is removed before every application instance has been updated, older versions of the application may begin failing because they still expect &lt;code&gt;full_name&lt;/code&gt; to exist.&lt;/p&gt;

&lt;p&gt;This becomes especially important during Rolling and Canary deployments where multiple versions of the application are communicating with the same database at the same time. Even Blue-Green deployments are not immune to this. Routing traffic back to the older environment does not automatically restore database changes that have already been applied.&lt;/p&gt;

&lt;p&gt;One common approach is the expand-and-contract pattern.&lt;/p&gt;

&lt;p&gt;First, expand the schema by introducing the new structure while keeping the existing one.&lt;/p&gt;

&lt;p&gt;Next, deploy application code that remains compatible with both versions and backfill older data where necessary.&lt;/p&gt;

&lt;p&gt;Once every application instance is running the newer version, the older schema or contract can safely be removed.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>devops</category>
      <category>software</category>
    </item>
    <item>
      <title>How Data Access Breaks Down Under Load</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Wed, 13 May 2026 15:27:18 +0000</pubDate>
      <link>https://dev.to/rahmannugar/how-data-access-breaks-down-under-load-2c7m</link>
      <guid>https://dev.to/rahmannugar/how-data-access-breaks-down-under-load-2c7m</guid>
      <description>&lt;p&gt;Most database performance problems do not begin with obviously slow queries.&lt;/p&gt;

&lt;p&gt;In fact, many systems look perfectly fine early on. Queries execute quickly, endpoints respond in a few milliseconds, and the database appears healthy. Even when something starts slowing down, the fix usually feels simple enough. Add an index, reduce a join, cache a response, and move on.&lt;/p&gt;

&lt;p&gt;That works for a while, which is part of what makes these issues deceptive.&lt;/p&gt;

&lt;p&gt;Because in production systems, performance problems rarely come from a single query being expensive in isolation. More often, they emerge from how the application accesses data as traffic increases and concurrency grows.&lt;/p&gt;

&lt;p&gt;A request that looks harmless locally may execute dozens of queries. Some of those queries may repeat unnecessarily. Some may fetch significantly more data than is actually needed. Others may hold connections longer than expected or compete with other transactions under load. None of these things look particularly dangerous on their own, but once they begin happening across hundreds or thousands of requests, the behavior of the system changes completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Real Problem&lt;/li&gt;
&lt;li&gt;Profiling Data Access Properly&lt;/li&gt;
&lt;li&gt;N+1 Query Problem&lt;/li&gt;
&lt;li&gt;Overfetching and Data Shape&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Connection Pools and Throughput&lt;/li&gt;
&lt;li&gt;Transactions, Isolation, and Contention&lt;/li&gt;
&lt;li&gt;Deadlocks&lt;/li&gt;
&lt;li&gt;How These Problems Compound&lt;/li&gt;
&lt;li&gt;Closing Thoughts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. The Real Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A database does not receive queries in isolation. It receives a continuous stream of queries generated by application code, and that stream is shaped entirely by how requests are implemented.&lt;/p&gt;

&lt;p&gt;As engineers, we often think in terms of individual operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch user&lt;/li&gt;
&lt;li&gt;fetch orders&lt;/li&gt;
&lt;li&gt;fetch comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the database does not see isolated operations. It sees concurrent requests, connection pressure, transaction overlap, repeated scans, lock contention between related requests, and large volumes of queries arriving continuously from the application layer.&lt;/p&gt;

&lt;p&gt;This is why systems that feel “fast” in development can degrade surprisingly quickly in production.&lt;/p&gt;

&lt;p&gt;Locally, you might test with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 users&lt;/li&gt;
&lt;li&gt;small datasets&lt;/li&gt;
&lt;li&gt;almost no concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production changes all of those assumptions simultaneously. The dataset becomes larger. More requests execute at the same time. Queries that looked harmless start repeating at scale. Operations that used to take milliseconds begin waiting on locks or connections instead of execution itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Profiling Data Access&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most engineers start profiling from the database. They look for slow queries, run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;, and try to optimize whatever looks expensive.&lt;/p&gt;

&lt;p&gt;That approach is incomplete, because it assumes the problem is within a single query. A query that takes 300ms once may not be a real problem. A query that takes 3ms but executes 200 times per request usually is.&lt;/p&gt;

&lt;p&gt;In reality, you need to start from the request.&lt;/p&gt;

&lt;p&gt;Take an endpoint and break it down in terms of what it actually does to the database. Not just the result it returns, but the work it generates.&lt;/p&gt;

&lt;p&gt;For example:&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;GET /dashboard

query_count: 74
total_db_time: 118ms
max_query_time: 6ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing there looks especially alarming until you realize the endpoint executes seventy-four queries.&lt;/p&gt;

&lt;p&gt;At low traffic, that may still feel fast enough. Under concurrency, it becomes a completely different problem because every request now generates far more database work than expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repetition Is Often Worse Than Latency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A very common production issue is not necessarily slow queries, but repeated queries.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;sql&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&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;executed over and over again within the same request can lead to N + 1 query problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution Time Is Not the Full Cost&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another thing that becomes important under load is separating execution time from wait time.&lt;/p&gt;

&lt;p&gt;A query might execute quickly once it reaches the database, but still spend significant time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;waiting for a connection&lt;/li&gt;
&lt;li&gt;waiting on another transaction&lt;/li&gt;
&lt;li&gt;waiting for locks to clear
&lt;strong&gt;When EXPLAIN Actually Helps&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; becomes useful once you confirm the issue is inside the query itself.&lt;/p&gt;

&lt;p&gt;For example, if a query unexpectedly performs a sequential scan over a very large table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on sessions
Rows Removed by Filter: 900000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that is usually a strong signal that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an index is missing&lt;/li&gt;
&lt;li&gt;the existing index is unusable&lt;/li&gt;
&lt;li&gt;or the query shape does not match the access pattern properly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But EXPLAIN only helps with localized inefficiency. It does not tell you whether the application is generating too many queries overall, saturating the connection pool, or overfetching aggressively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. N+1 Query Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The N+1 problem is one of the easiest ways to accidentally overload a database without realizing it early enough.&lt;/p&gt;

&lt;p&gt;The reason it slips through so often is because the code usually looks somewhat reasonable.&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="nx"&gt;ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for &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;user&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;Nothing about this feels obviously dangerous during development.&lt;/p&gt;

&lt;p&gt;If there are 10 users, this endpoint executes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query to fetch users&lt;/li&gt;
&lt;li&gt;10 additional queries to fetch posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue is not that the queries are individually slow. The issue is that the amount of database work now scales with the number of records returned.&lt;/p&gt;

&lt;p&gt;As the dataset grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 users → 101 queries&lt;/li&gt;
&lt;li&gt;500 users → 501 queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The endpoint becomes progressively more expensive simply because more rows are returned.&lt;/p&gt;

&lt;p&gt;Under concurrency, this gets worse very quickly because every request repeats the same inefficient access pattern against the database.&lt;/p&gt;

&lt;p&gt;One common mistake is assuming that concurrency fixes the problem.&lt;/p&gt;

&lt;p&gt;For example:&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="nx"&gt;ts&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may reduce the response time of the endpoint because the queries now execute concurrently, but the application is still generating one query per user.&lt;/p&gt;

&lt;p&gt;The access pattern itself has not changed.&lt;/p&gt;

&lt;p&gt;In some cases, this can actually make things worse because the application now sends many related queries to the database at the same time instead of sequentially. Under enough traffic, that creates even more connection pressure and query contention.&lt;/p&gt;

&lt;p&gt;The proper fix is usually to fetch related data via joins or batch queries.&lt;/p&gt;

&lt;p&gt;For example:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;or&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userIds&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;&lt;strong&gt;4. Overfetching and Data Shape&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In many systems, the problem is not necessarily that queries are slow. The problem is that queries return significantly more data than the request actually needs.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selecting entire rows instead of specific fields&lt;/li&gt;
&lt;li&gt;loading large relations unnecessarily&lt;/li&gt;
&lt;li&gt;returning deeply nested objects to clients that only need summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ORMs make this particularly easy because queries are abstracted with language code.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;include:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;posts:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might technically solve an N+1 problem while still introducing unnecessary work elsewhere.&lt;/p&gt;

&lt;p&gt;Maybe the client only needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;post title&lt;/li&gt;
&lt;li&gt;createdAt&lt;/li&gt;
&lt;li&gt;latest 5 posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;but the query now returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full post bodies&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;all historical posts&lt;/li&gt;
&lt;li&gt;nested relations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At low traffic, this usually feels harmless however under load, it becomes expensive because every unnecessary field now contributes to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;larger payloads&lt;/li&gt;
&lt;li&gt;higher memory usage&lt;/li&gt;
&lt;li&gt;longer serialization time&lt;/li&gt;
&lt;li&gt;increased network transfer&lt;/li&gt;
&lt;li&gt;more cache pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Indexes and Access Patterns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of engineers think of indexes as generic speed improvements, but indexes are really about access patterns. They determine how efficiently the database can locate data as datasets grow. Without an index, the database often has no efficient way to locate matching rows, so it scans through the table looking for them.&lt;/p&gt;

&lt;p&gt;With an index, the database can navigate directly to the relevant portion of data instead of traversing everything.&lt;/p&gt;

&lt;p&gt;Imagine an order table with 1000 rows, scanning the entire DB to return orders for a user may feel fast here however such query may become extremely expensive at 10 million rows even though the query itself never changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composite Indexes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Composite indexes become important once queries filter or sort across multiple fields.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@index([userId, revoked])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works well for queries like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to note about composite indexes is that they are order-sensitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@index([userId, revoked])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would work for queries like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;userId&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;but not&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;revoked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;userId&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;&lt;strong&gt;Indexes Are Not Free&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Indexes improve reads by introducing additional work elsewhere.&lt;/p&gt;

&lt;p&gt;Every insert, update, or delete must now maintain those index structures as well.&lt;/p&gt;

&lt;p&gt;Too many indexes can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow writes&lt;/li&gt;
&lt;li&gt;increase storage usage&lt;/li&gt;
&lt;li&gt;increase memory pressure&lt;/li&gt;
&lt;li&gt;create unnecessary maintenance overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct pattern is to think in terms of tradeoffs, you ideally want to index read heavy table columns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Connection Pools and Throughput&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every query requires a database connection to fetch data or mutate state. These connections are not lightweight resources. They consume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;CPU scheduling overhead&lt;/li&gt;
&lt;li&gt;active database resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without pooling, applications can overwhelm the database very quickly under concurrency. Instead of opening a new connection per request the application maintains a reusable pool of active connections shared across requests.&lt;/p&gt;

&lt;p&gt;At that point, the system effectively becomes a queue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requests wait for available connections&lt;/li&gt;
&lt;li&gt;queries execute&lt;/li&gt;
&lt;li&gt;connections return to the pool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pool Saturation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once all connections are busy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new queries must wait&lt;/li&gt;
&lt;li&gt;request latency increases&lt;/li&gt;
&lt;li&gt;timeouts begin appearing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one reason systems sometimes feel slow even when individual queries are relatively fast.&lt;/p&gt;

&lt;p&gt;The queries themselves may execute quickly once they reach the database, but they spend significant time waiting for available connections beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pool Size Trade-offs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A pool that is too small increases queueing and latency meanwhile pool too large creates a different problem.&lt;/p&gt;

&lt;p&gt;Too many concurrent connections increase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context switching&lt;/li&gt;
&lt;li&gt;lock contention&lt;/li&gt;
&lt;li&gt;database scheduling overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually the database spends more time coordinating active work than executing queries efficiently.&lt;/p&gt;

&lt;p&gt;This is also why tools like PgBouncer become important in high concurrency systems. They reduce connection overhead and help applications manage large numbers of requests more efficiently.&lt;/p&gt;

&lt;p&gt;Pooling, however, does not fix inefficient access patterns. It only controls how much concurrent pressure reaches the database at once.&lt;/p&gt;

&lt;p&gt;A badly optimized endpoint behind a connection pool is still a badly optimized endpoint. The pool simply delays the point at which the system begins struggling.&lt;/p&gt;

&lt;p&gt;This distinction becomes important because many performance issues that appear to be “database problems” are actually workload problems created by the application layer itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Transactions, Isolation, and Contention&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Transactions refer to set of operations that fail or succeed together. Relational databases utilizes transactions(if configured) to carry out DB work. They ensure atomicity but alone do not automatically guarantee correctness under concurrency.&lt;/p&gt;

&lt;p&gt;Once systems start handling large amounts of concurrent traffic, correctness becomes just as important as performance.&lt;/p&gt;

&lt;p&gt;Multiple requests may now attempt to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;update the same rows&lt;/li&gt;
&lt;li&gt;read partially changing data&lt;/li&gt;
&lt;li&gt;modify shared resources simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without proper transaction control, this can create subtle inconsistencies that are extremely difficult to debug once they appear in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Isolation levels determine how transactions behave while other transactions are executing at the same time.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can one transaction read uncommitted changes from another?&lt;/li&gt;
&lt;li&gt;can the same query return different results within the same transaction?&lt;/li&gt;
&lt;li&gt;can two transactions overwrite each other’s updates?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems become more visible once systems become highly concurrent.&lt;/p&gt;

&lt;p&gt;One common example is the lost update problem.&lt;/p&gt;

&lt;p&gt;Imagine two concurrent requests reading the same balance:&lt;br&gt;
&lt;/p&gt;

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

Transaction A reads 100
Transaction B reads 100

Transaction A updates to 50
Transaction B updates to 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final balance becomes 50 instead of 0 because both transactions operated on stale state.&lt;/p&gt;

&lt;p&gt;This is why isolation levels exist.&lt;/p&gt;

&lt;p&gt;Lower isolation levels improve throughput because transactions coordinate less aggressively, but they also allow more concurrency anomalies.&lt;/p&gt;

&lt;p&gt;Higher isolation levels improve correctness, but increase coordination overhead and reduce concurrency; meaning concurrent requests run similarly to a one after another flow.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;analytics systems may tolerate slightly stale reads&lt;/li&gt;
&lt;li&gt;payment systems usually cannot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing that becomes important under heavy traffic is understanding that transactions also hold resources while they remain open.&lt;/p&gt;

&lt;p&gt;Long-running transactions increase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lock duration&lt;/li&gt;
&lt;li&gt;contention&lt;/li&gt;
&lt;li&gt;connection occupancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;which means poorly designed transaction boundaries can indirectly degrade the entire system under load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Deadlocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deadlocks occur when transactions wait on each other in a cycle, and neither can proceed. They occur when multiple transactions access shared resources in different orders and end up waiting on each other indefinitely.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Transaction A locks Row 1
Transaction B locks Row 2

Transaction A now waits for Row 2
Transaction B now waits for Row 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither transaction can continue so the database resolves the deadlock by aborting one of them.&lt;/p&gt;

&lt;p&gt;Deadlocks are a sign that the system does not have a consistent way of coordinating access.&lt;/p&gt;

&lt;p&gt;One of the most effective ways to reduce deadlocks is maintaining consistent lock ordering.&lt;/p&gt;

&lt;p&gt;For example, if multiple transactions must update two rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;always lock the smaller ID first&lt;/li&gt;
&lt;li&gt;always acquire resources in the same sequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping transactions short also matters.&lt;/p&gt;

&lt;p&gt;The longer a transaction remains open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the longer locks are held&lt;/li&gt;
&lt;li&gt;the more overlap exists with other transactions&lt;/li&gt;
&lt;li&gt;the higher the probability of contention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;9. How These Problems Compound&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing that makes production performance difficult is that these issues rarely appear independently.&lt;/p&gt;

&lt;p&gt;A system may begin with a small N+1 pattern.&lt;/p&gt;

&lt;p&gt;That increases query volume.&lt;/p&gt;

&lt;p&gt;Higher query volume increases connection usage.&lt;/p&gt;

&lt;p&gt;Connection pressure increases wait time.&lt;/p&gt;

&lt;p&gt;Longer waits increase transaction duration.&lt;/p&gt;

&lt;p&gt;Longer transactions increase lock contention.&lt;/p&gt;

&lt;p&gt;More contention increases the likelihood of deadlocks and throughput collapse.&lt;/p&gt;

&lt;p&gt;At that point, the system is no longer failing because of one slow query. The entire access pattern of the application has become unstable under concurrency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Closing Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most database performance issues are not caused by one catastrophic query. They usually emerge gradually from small inefficiencies that compound under load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeated queries&lt;/li&gt;
&lt;li&gt;excessive payloads&lt;/li&gt;
&lt;li&gt;inefficient indexing&lt;/li&gt;
&lt;li&gt;poor transaction boundaries&lt;/li&gt;
&lt;li&gt;uncontrolled concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of these systems still feel “fast” early on, which is part of why these problems are easy to ignore initially.&lt;/p&gt;

&lt;p&gt;The challenge is that production traffic changes the behavior of the system completely. Queries that once felt harmless begin repeating at scale, transactions overlap more aggressively, connection pressure increases, and small inefficiencies start compounding into system-wide bottlenecks.&lt;/p&gt;

&lt;p&gt;At that point, database performance is no longer just about whether a query is fast. It becomes about whether the overall pattern of data access can still hold up under concurrency.&lt;/p&gt;

</description>
      <category>database</category>
      <category>backenddevelopment</category>
      <category>software</category>
    </item>
    <item>
      <title>Idempotency Is Not Enough: Designing Deterministic Payment Systems</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Tue, 14 Apr 2026 05:49:49 +0000</pubDate>
      <link>https://dev.to/rahmannugar/idempotency-is-not-enough-designing-deterministic-payment-systems-4514</link>
      <guid>https://dev.to/rahmannugar/idempotency-is-not-enough-designing-deterministic-payment-systems-4514</guid>
      <description>&lt;p&gt;In payment systems, duplicate processing is not a theoretical problem. It happens in ordinary, boring ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user taps a payment button twice.&lt;/li&gt;
&lt;li&gt;The network times out after the server has already processed the request.&lt;/li&gt;
&lt;li&gt;A mobile client retries automatically.&lt;/li&gt;
&lt;li&gt;A background worker replays a failed renewal.&lt;/li&gt;
&lt;li&gt;A webhook arrives again.
If the system is not designed carefully, any of those events can result in the same transaction being processed twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recently worked on tightening the payment architecture in my company’s product, and this was one of the main problems I had to solve.&lt;/p&gt;

&lt;p&gt;The usual answer is idempotency keys, and they are important. But on their own, they are not enough.&lt;/p&gt;

&lt;p&gt;Duplicate processing in payment systems rarely comes only from obvious user behavior. More often, it emerges from retries, uncertain network boundaries, repeated webhooks, and recovery jobs. Idempotency keys help, but they only protect the request boundary. If the same logical transaction can still arrive with a different key, the system is not fully safe.&lt;/p&gt;

&lt;p&gt;What I needed was not a best-effort duplicate check. I needed a payment flow that remained correct under retries, network uncertainty, repeated webhooks, and background recovery jobs.&lt;/p&gt;

&lt;p&gt;This article explains the design pattern I implemented at my company to make payment flows deterministic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;client-generated idempotency keys for user actions&lt;/li&gt;
&lt;li&gt;server-generated idempotency keys for system retries&lt;/li&gt;
&lt;li&gt;business-level uniqueness enforced in the database&lt;/li&gt;
&lt;li&gt;the database acting as the final source of truth&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Real Problem&lt;/li&gt;
&lt;li&gt;What Idempotency Actually Solves&lt;/li&gt;
&lt;li&gt;Why Idempotency Alone Is Not Enough&lt;/li&gt;
&lt;li&gt;The Second Layer: Business-Level Uniqueness&lt;/li&gt;
&lt;li&gt;Client Keys vs Server Keys&lt;/li&gt;
&lt;li&gt;Retry Flows and Failure Recovery&lt;/li&gt;
&lt;li&gt;Database as Final Source of Truth&lt;/li&gt;
&lt;li&gt;The Design Trade-off&lt;/li&gt;
&lt;li&gt;Closing Thoughts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When people discuss duplicate payments, they often frame it as a user-behavior problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What if the user clicks twice?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is only one version of the problem.&lt;/p&gt;

&lt;p&gt;In practice, duplicate financial processing usually comes from distributed system behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client does not know whether the first request completed&lt;/li&gt;
&lt;li&gt;the server crashes after persisting state but before responding&lt;/li&gt;
&lt;li&gt;a job runner retries after a timeout&lt;/li&gt;
&lt;li&gt;an external payment provider sends the same event more than once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the question is no longer “did this request arrive twice?”&lt;/p&gt;

&lt;p&gt;The real question is:&lt;/p&gt;

&lt;p&gt;“Can this business operation be committed twice?”&lt;/p&gt;

&lt;p&gt;This matters, because requests and business operations are not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Idempotency Actually Solves
&lt;/h2&gt;

&lt;p&gt;Let's first define what the term means. Idempotency is a principle that ensures an operation can be performed multiple times without altering the result after the first execution. Idempotency keys enforce this principle by making repeated requests return the same outcome.&lt;/p&gt;

&lt;p&gt;A client generates a key, attaches it to a write operation, and if the same request is replayed with the same key, the server returns the original result instead of processing it again.&lt;/p&gt;

&lt;p&gt;This is extremely useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network retries&lt;/li&gt;
&lt;li&gt;client restarts&lt;/li&gt;
&lt;li&gt;uncertain response delivery&lt;/li&gt;
&lt;li&gt;safe replay of a user-initiated action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, idempotency protects the request boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why Idempotency Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Idempotency keys are designed to deduplicate requests, not business intent.  They guarantee that the same key won't trigger the same operation twice but prove futile if the same transaction is submitted with a different key. &lt;/p&gt;

&lt;p&gt;In practice, this happens constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a client generates a fresh key on every retry&lt;/li&gt;
&lt;li&gt;two devices initiate the same action independently&lt;/li&gt;
&lt;li&gt;the same business intent is replayed from another workflow&lt;/li&gt;
&lt;li&gt;a user refreshes and restarts the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the system relies only on idempotency keys, then a different key can make the same transaction appear new.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Second Layer: Business-Level Uniqueness
&lt;/h2&gt;

&lt;p&gt;To close that gap, we need a second rule:&lt;/p&gt;

&lt;p&gt;the system must define what makes a transaction truly unique at the business level and enforce that rule in storage.&lt;/p&gt;

&lt;p&gt;This design assumes that each business operation is anchored to a stable identifier (e.g. orderId, cartId, or subscriptionId) that is reused across retries. Without this, the system cannot deterministically detect duplicates.&lt;/p&gt;

&lt;p&gt;This is where many systems stay too soft. They validate in application code, or they “check before insert,” but that still leaves room for race conditions.&lt;/p&gt;

&lt;p&gt;The stronger approach is to define deterministic uniqueness around the transaction itself.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an order payment for a specific actor and order ID&lt;/li&gt;
&lt;li&gt;a subscription renewal for a specific subscription and billing period&lt;/li&gt;
&lt;li&gt;a refund for a specific parent transaction&lt;/li&gt;
&lt;li&gt;a slot renewal for a specific slot and renewal cycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that business identity is clear, the database can enforce it with unique constraints or equivalent transaction-safe guards.&lt;/p&gt;

&lt;p&gt;That gives the system a multi protection layer against duplicate processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotency key uniqueness at the request level&lt;/li&gt;
&lt;li&gt;business identity uniqueness at the operation level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Client Keys vs Server Keys
&lt;/h2&gt;

&lt;p&gt;Not every transaction originates from the same place, so not every key should be generated the same way.&lt;/p&gt;

&lt;p&gt;For user actions, client-generated keys make sense.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting a checkout&lt;/li&gt;
&lt;li&gt;upgrading a plan&lt;/li&gt;
&lt;li&gt;purchasing an add-on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client is the originator of the action, so it should generate the request identity.&lt;/p&gt;

&lt;p&gt;For system actions, server-generated keys are more appropriate.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retrying a failed subscription renewal&lt;/li&gt;
&lt;li&gt;replaying a queued refund job&lt;/li&gt;
&lt;li&gt;creating a new internal attempt after a provider error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these flows, the system itself is the actor. The server owns the retry logic, so the server should generate the retry key.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Retry Flows and Failure Recovery
&lt;/h2&gt;

&lt;p&gt;Retries are where payment systems get messy very quickly.&lt;/p&gt;

&lt;p&gt;If retries are not modeled explicitly, the system often mutates the same record over and over, and after a while it becomes difficult to answer basic questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many attempts were made?&lt;/li&gt;
&lt;li&gt;which provider reference belongs to which attempt?&lt;/li&gt;
&lt;li&gt;what actually failed?&lt;/li&gt;
&lt;li&gt;what was retried automatically versus manually?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleaner approach is to treat each attempt as its own durable transaction record, linked back to the original business operation.&lt;/p&gt;

&lt;p&gt;That gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stable business identity for the logical payment&lt;/li&gt;
&lt;li&gt;a separate attempt history for operational recovery&lt;/li&gt;
&lt;li&gt;bounded retries instead of untracked replay loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially important in subscription billing, where retries can happen long after the original charge attempt and often under degraded network or provider conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Database as Final Source of Truth
&lt;/h2&gt;

&lt;p&gt;Application code is not the strongest place to enforce financial correctness.&lt;/p&gt;

&lt;p&gt;It is useful, but it is not final.&lt;/p&gt;

&lt;p&gt;Two requests can race.&lt;br&gt;
Two workers can run at the same time.&lt;br&gt;
Two app instances can both decide something “does not exist yet.”&lt;/p&gt;

&lt;p&gt;The only layer that sees the final committed write is the database.&lt;/p&gt;

&lt;p&gt;That is why the database must be the last word on transaction uniqueness.&lt;/p&gt;

&lt;p&gt;This is the principle I care about most in payment systems:&lt;/p&gt;

&lt;p&gt;correctness should be deterministic, not heuristic.&lt;/p&gt;

&lt;p&gt;A heuristic says:&lt;/p&gt;

&lt;p&gt;“We usually catch duplicates.”&lt;/p&gt;

&lt;p&gt;A deterministic system says:&lt;/p&gt;

&lt;p&gt;“This duplicate cannot be committed.”&lt;/p&gt;

&lt;p&gt;That difference becomes more important as the system grows, because retries, workers, webhooks, and concurrent clients all increase over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Design Trade-off
&lt;/h2&gt;

&lt;p&gt;This design is stricter than a simple idempotency-key implementation.&lt;/p&gt;

&lt;p&gt;It introduces more explicit transaction modeling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request identity&lt;/li&gt;
&lt;li&gt;business identity&lt;/li&gt;
&lt;li&gt;attempt history&lt;/li&gt;
&lt;li&gt;retry limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is extra structure, but in financial flows, that structure pays for itself.&lt;/p&gt;

&lt;p&gt;Without it, you eventually end up debugging ambiguity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;was this a duplicate charge or a second attempt?&lt;/li&gt;
&lt;li&gt;did the user retry or did the worker retry?&lt;/li&gt;
&lt;li&gt;is this the same renewal cycle or the next one?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the system treats transactions as durable business objects rather than transient HTTP events, those questions become much easier to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Idempotency keys are valuable, but they are only one part of a safe payment design.&lt;/p&gt;

&lt;p&gt;If a different key can still trigger the same transaction twice, then the system is not truly protected.&lt;/p&gt;

&lt;p&gt;The stronger model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use idempotency keys to make retries safe&lt;/li&gt;
&lt;li&gt;define business-level uniqueness for the underlying transaction&lt;/li&gt;
&lt;li&gt;enforce that uniqueness in the database&lt;/li&gt;
&lt;li&gt;let the database act as the final source of truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Idempotency keys are still one of the most useful tools in payment systems however I do not think they should be treated as the full solution.&lt;/p&gt;

&lt;p&gt;They make repeated requests safer. They do not, on their own, define the true identity of a financial transaction.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>payments</category>
      <category>software</category>
    </item>
    <item>
      <title>Owlyn: Building a Multimodal Agent Ecosystem for Live Technical Interviews and Real-Time Assistance #GeminiLiveAgentChallenge</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Mon, 16 Mar 2026 17:48:40 +0000</pubDate>
      <link>https://dev.to/rahmannugar/owlyn-building-a-multimodal-agent-ecosystem-for-live-technical-interviews-and-real-time-assistance-15hg</link>
      <guid>https://dev.to/rahmannugar/owlyn-building-a-multimodal-agent-ecosystem-for-live-technical-interviews-and-real-time-assistance-15hg</guid>
      <description>&lt;p&gt;The current landscape of technical hiring is bottlenecked by a fundamental scalability problem. Organizations receiving hundreds of applications per role find it impossible to conduct live, high-quality interviews for every candidate, often resorting to cold, non-immersive recordings or static tests. These traditional "AI interviews" are neither live nor engaging—they strip away the conversational nuance that defines a great engineer and leave both the company and the candidate with a fragmented view of technical potential.&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;Owlyn&lt;/strong&gt; to solve this gap using real-time multimodal intelligence. Instead of a static AI assessment, Owlyn operates as an autonomous agent ecosystem capable of seeing, hearing, and reasoning about a candidate’s live workspace. By leveraging the Gemini Live API, Owlyn conducts real-time technical interviews and provides a persistent assistant mode that can see, hear, and interact with sub-second latency. Every interaction is synchronized with live transcripts, ensuring the system is both high-fidelity and accessible across every workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/D8g6On2PBhM?si=jiWdYYfitfzeh1Nl" rel="noopener noreferrer"&gt;Watch the Owlyn Demo on YouTube&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This piece of content was written by me, &lt;strong&gt;Abdulrahmon Adenuga&lt;/strong&gt; (&lt;a class="mentioned-user" href="https://dev.to/rahmannugar"&gt;@rahmannugar&lt;/a&gt;), along with &lt;strong&gt;Akeem Adetunji&lt;/strong&gt; and &lt;strong&gt;Mosimiloluwa Adebisi&lt;/strong&gt; and created for the purposes of entering the Google &lt;strong&gt;#GeminiLiveAgentChallenge&lt;/strong&gt; hackathon. It covers how we built Owlyn using Google AI models and Google Cloud.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Core Objectives&lt;/li&gt;
&lt;li&gt;System Architecture&lt;/li&gt;
&lt;li&gt;The Multi-Agent Protocol&lt;/li&gt;
&lt;li&gt;Live Workflows: Interview, Monitoring, and Assistant&lt;/li&gt;
&lt;li&gt;Recruitment Management: Dashboards and Talent Pools&lt;/li&gt;
&lt;li&gt;Real-Time Multimodal Pipelines&lt;/li&gt;
&lt;li&gt;Security: The Sentinel Mode&lt;/li&gt;
&lt;li&gt;Engineering Decisions&lt;/li&gt;
&lt;li&gt;Closing Thoughts and Future Roadmap&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The Core Objectives
&lt;/h2&gt;

&lt;p&gt;Existing AI interview tools often fail because they are built as wrappers around static LLMs, leading to two major deal-breakers: &lt;strong&gt;hallucination&lt;/strong&gt; and &lt;strong&gt;latency&lt;/strong&gt;. If an AI takes 5 seconds to respond, the conversation is dead. If it "guesses" what your code does instead of analyzing its logic, it loses all technical authority.&lt;/p&gt;

&lt;p&gt;To solve this, we defined four design pillars for Owlyn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Zero-Latency&lt;/strong&gt;: Using Gemini Live to achieve sub-second response times, this eliminates the "awkward silence" typical of LLM-based bots, ensuring the conversation maintains the natural momentum of a real-world technical discussion.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multimodal Reasoning&lt;/strong&gt;: The agent must do more than listen; it must "see" the workspace. By streaming the screen feed, the agent can react to a candidate's cursor movements or a logic error in a whiteboard diagram.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Inclusive Accessibility&lt;/strong&gt;: Building a system that is accessible to everyone. This means supporting multiple spoken languages and providing live transcripts for candidates with hearing disabilities, ensuring that automation doesn't come at the cost of inclusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. System Architecture
&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.amazonaws.com%2Fuploads%2Farticles%2F6v2ato3amrknj0we5bg1.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.amazonaws.com%2Fuploads%2Farticles%2F6v2ato3amrknj0we5bg1.png" alt="System Architecture I" width="800" height="1200"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fggoh8mhp3ja1wkpq0wal.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fggoh8mhp3ja1wkpq0wal.jpg" alt="System Architecture II" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Owlyn is designed as a distributed, real-time multimodal system composed of four major layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Electron Client Layer&lt;/li&gt;
&lt;li&gt;Backend Orchestration Layer&lt;/li&gt;
&lt;li&gt;Worker Agent Layer&lt;/li&gt;
&lt;li&gt;Infrastructure &amp;amp; AI Layer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation allows the system to maintain sub-second conversational latency, while supporting multiple AI agents analyzing different signals simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Electron Client Layer
&lt;/h3&gt;

&lt;p&gt;The client application is built with Electron + React, which allows Owlyn to access system-level capabilities that are unavailable in the browser.&lt;/p&gt;

&lt;p&gt;The Electron frontend is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capturing camera, microphone, and screen feeds&lt;/li&gt;
&lt;li&gt;Rendering the Monaco coding workspace&lt;/li&gt;
&lt;li&gt;Streaming audio/video via WebRTC&lt;/li&gt;
&lt;li&gt;Sending application events to the backend via HTTPS REST&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client communicates with the backend in two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTPS / REST&lt;/strong&gt;&lt;br&gt;
Used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Session creation&lt;/li&gt;
&lt;li&gt;Interview configuration&lt;/li&gt;
&lt;li&gt;transcript synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;LiveKit WebRTC&lt;/strong&gt;&lt;br&gt;
Used for low-latency real-time streams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;microphone audio&lt;/li&gt;
&lt;li&gt;webcam video&lt;/li&gt;
&lt;li&gt;workspace signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These streams are routed to the backend orchestration layer and AI agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Backend Orchestration Layer
&lt;/h3&gt;

&lt;p&gt;The backend is built using Spring Boot and acts as the central orchestrator of the entire system.&lt;/p&gt;

&lt;p&gt;Rather than allowing each AI agent to independently connect to the client, all signals pass through the backend first. This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consistent session state&lt;/li&gt;
&lt;li&gt;controlled AI communication&lt;/li&gt;
&lt;li&gt;centralized logging and monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend exposes two internal interfaces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REST API&lt;/strong&gt;&lt;br&gt;
Handles standard application workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interview creation&lt;/li&gt;
&lt;li&gt;candidate session management&lt;/li&gt;
&lt;li&gt;transcript storage&lt;/li&gt;
&lt;li&gt;session metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Internal API&lt;/strong&gt;&lt;br&gt;
Used for agent-to-agent communication and system orchestration.&lt;br&gt;
This internal API connects to the Assessor Agent, which performs post-interview analysis and scoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Worker Agent Layer
&lt;/h3&gt;

&lt;p&gt;Owlyn runs several specialized Python worker agents responsible for processing multimodal signals during a session.&lt;br&gt;
These agents operate independently from the core backend to keep real-time processing lightweight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LiveKit Agent&lt;/strong&gt;&lt;br&gt;
The LiveKit Agent connects to the LiveKit WebRTC stream and manages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real-time voice conversation&lt;/li&gt;
&lt;li&gt;audio streaming to Gemini Live&lt;/li&gt;
&lt;li&gt;returning AI responses to the candidate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This forms the primary conversational loop of the interview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proctor Sentinel&lt;/strong&gt;&lt;br&gt;
The Integrity Sentinel monitors the webcam feed to ensure the session remains secure.&lt;br&gt;
Using Gemini Vision, it detects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unauthorized devices&lt;/li&gt;
&lt;li&gt;additional people in frame&lt;/li&gt;
&lt;li&gt;suspicious behavior&lt;/li&gt;
&lt;li&gt;environmental anomalies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any violation is immediately flagged and logged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workspace Sentinel&lt;/strong&gt;&lt;br&gt;
The Workspace Sentinel observes the candidate’s coding environment, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monaco editor&lt;/li&gt;
&lt;li&gt;whiteboard interactions&lt;/li&gt;
&lt;li&gt;cursor behavior&lt;/li&gt;
&lt;li&gt;code structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It continuously analyzes implementation logic and forwards observations to the Interviewer agent so the conversation can react to the candidate’s code in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Infrastructure and AI Layer
&lt;/h3&gt;

&lt;p&gt;The final layer provides the persistent infrastructure and AI services that power the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dockerized PostgreSQL &amp;amp; Redis&lt;/strong&gt;&lt;br&gt;
We utilize &lt;strong&gt;Docker&lt;/strong&gt; to containerize our data layer, ensuring a consistent and isolated environment for both PostgreSQL and Redis. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt;: Handles durable storage for interview sessions, transcripts, reports, and user data. It is accessed through the Spring Boot backend via JPA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt;: Stores live session state (transcript buffers, agent context, security flags) for sub-millisecond updates during live conversations.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on Backend Deployment&lt;/strong&gt;: The Owlyn backend (orchestration layer and worker agents) is hosted on &lt;strong&gt;Google Cloud Virtual Machines (VMs)&lt;/strong&gt;. This provides the stable, high-performance environment necessary for sub-second multimodal processing and session management.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;LiveKit Server&lt;/strong&gt;&lt;br&gt;
LiveKit acts as the real-time media server for Owlyn.&lt;br&gt;
It manages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebRTC signaling&lt;/li&gt;
&lt;li&gt;audio/video transport&lt;/li&gt;
&lt;li&gt;stream synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows voice conversations to occur with sub-second latency, which is critical for maintaining natural dialogue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini AI&lt;/strong&gt;&lt;br&gt;
Owlyn integrates multiple Gemini models for specialized reasoning tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemini Live API → real-time voice conversation&lt;/li&gt;
&lt;li&gt;Gemini 3.1 Pro → deeper reasoning and evaluation&lt;/li&gt;
&lt;li&gt;Gemini 3 Flash → lightweight real-time inference&lt;/li&gt;
&lt;li&gt;Gemini Vision → visual analysis of the workspace and webcam
Each model powers a different agent in the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.5 Cross-System Communication Flow
&lt;/h3&gt;

&lt;p&gt;During a live interview session, the system operates as a continuous pipeline:&lt;/p&gt;

&lt;p&gt;The Electron client streams audio/video via LiveKit.&lt;/p&gt;

&lt;p&gt;The Spring Boot backend orchestrates session state.&lt;/p&gt;

&lt;p&gt;Python agents process multimodal signals in parallel.&lt;/p&gt;

&lt;p&gt;Signals are routed to Gemini AI models for reasoning.&lt;/p&gt;

&lt;p&gt;Results are returned through LiveKit to the candidate in real time.&lt;/p&gt;

&lt;p&gt;Redis stores live context while PostgreSQL stores persistent records.&lt;/p&gt;

&lt;p&gt;This architecture allows Owlyn to maintain low latency, contextual awareness, and modular AI reasoning, enabling the system to behave less like a scripted bot and more like a real technical interviewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Multi-Agent Protocol
&lt;/h2&gt;

&lt;p&gt;Owlyn is built on a &lt;strong&gt;decoupled multi-agent architecture&lt;/strong&gt;. We designed the system from the ground up using specialized Gemini instances for distinct tasks; voice interaction, workspace vision, and real-time code analysis rather than relying on a single monolithic agent. This ensures that the interviewer remains grounded by the candidate's actual workspace signals while maintaining sub-second conversational latency.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Orchestrator&lt;/strong&gt; is our server. Since Gemini agents can't directly talk to each other without a shared context, the Orchestrator acts as the central router. It receives video and audio via WebRTC, pipes them to the correct Gemini model, and then relays &lt;strong&gt;context&lt;/strong&gt; between the different agents. This ensures the interviewer can react to a failing test case or a specific logic choice in real-time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent 1: The Question Generator (Gemini 3 Flash Preview)&lt;/strong&gt;: This agent runs during the interview creation phase on the management dashboard. It analyzes the job role and requirement details to draft specific technical challenges and coding tasks. This ensures the interviewer (Agent 2) has a tailored set of objectives ready before the candidate even starts the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 2: The Interviewer (Gemini Live)&lt;/strong&gt;: This is the conversational agent the candidate hears. It handles the voice loop with sub-second latency, maintaining a natural dialogue flow throughout the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 3: The Sentinels (Gemini 3.1 Flash Lite)&lt;/strong&gt;: These are the specialized "eyes" and "ears" of the system.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrity Sentinel&lt;/strong&gt;: Processes the video feed to monitor session security and detect unauthorized activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workspace Sentinel&lt;/strong&gt;: Observes the Monaco editor and whiteboard. It analyzes the implementation logic in real-time, providing deep structural insights to the interviewer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 4: The Technical Assessor (Gemini 3)&lt;/strong&gt;: Once the interview ends, this agent takes the full transcript and the logic reasoning logs to generate a structured JSON report. It looks purely at the data to give an unbiased score.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    %% Phase 1: Setup
    subgraph Setup ["1. Pre-Interview Setup"]
        Start(Job Role) --&amp;gt; A1[Agent 1: Question Generator]
        A1 -- "Drafts" --&amp;gt; Tasks(Technical Tasks)
    end

    %% Phase 2: Live
    subgraph Interaction ["2. Live Interaction"]
        Candidate((Candidate)) &amp;lt;--&amp;gt; |LiveKit WebRTC| Orch[Spring Boot Orchestrator]
        Orch &amp;lt;--&amp;gt; A2[Agent 2: Gemini Live Interviewer]
        Orch &amp;lt;--&amp;gt; A3[Agent 3: Workspace Sentinels]
        A3 -- "Visual &amp;amp; Code Feed" --&amp;gt; A2
    end

    %% Phase 3: Reporting
    subgraph Evaluation ["3. Final Scoring"]
        Interaction -- "Session Logs" --&amp;gt; Data(Transcripts &amp;amp; Context)
        Data --&amp;gt; A4[Agent 4: Technical Assessor]
        A4 -- "Report" --&amp;gt; Result(Unbiased Score)
    end

    %% Logic Flow
    Tasks -.-&amp;gt; A2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This "Agent-to-Agent" handoff is what makes Owlyn feel smart. When Agent 3 identifies a logic error in your code, it tells Agent 2: &lt;em&gt;"Hey, their solution might have a performance issue."&lt;/em&gt; Agent 2 then asks the candidate: &lt;em&gt;"I noticed your current approach might have some performance challenges. Can you walk me through your time complexity?"&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Persona Customization: Defining the Interviewer
&lt;/h3&gt;

&lt;p&gt;We built a &lt;strong&gt;Persona Engine&lt;/strong&gt; to move away from a "one-size-fits-all" agent ecosystem. Recruiters can configure their agents' behavior through several technical levers in the management dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linguistic Localization&lt;/strong&gt;: The spoken language can be set per session, supporting &lt;strong&gt;English, German, Spanish, French&lt;/strong&gt;, and several other locales for a global candidate pool.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Behavioral Scalars&lt;/strong&gt;: Instead of basic prompts, we use weighted scores for &lt;strong&gt;Empathy&lt;/strong&gt;, &lt;strong&gt;Analytical Depth&lt;/strong&gt;, and &lt;strong&gt;Directness&lt;/strong&gt;. These values are injected into the agent's core instructions to shift the tone from a supportive guide to a rigorous technical evaluator.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document-Based Knowledge&lt;/strong&gt;: Recruiters can upload PDFs or DOCX files—such as internal engineering rubrics or company values—which the system parses and uses to inform the agent's specific technical knowledge during the session.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Live Workflows: Interview, Monitoring, and Assistant
&lt;/h2&gt;

&lt;p&gt;We didn't just stop at an interview screen. We built four distinct ways to use Owlyn. Across all modes, we prioritized accessibility through &lt;strong&gt;Live Transcripts&lt;/strong&gt;. For developers with hearing disabilities or those in noisy environments, Owlyn provides a threaded, real-time transcript of every word the agent says. This ensures the system remains inclusive and that the agent's logic can be read and reviewed as it happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. The Interview Workspace
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fyy0pegydrgcb551hv310.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.amazonaws.com%2Fuploads%2Farticles%2Fyy0pegydrgcb551hv310.png" alt="Interview workspace" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
This is the core experience. The candidate has a professional-grade workspace with the &lt;strong&gt;Monaco Editor&lt;/strong&gt;, a canvas-based &lt;strong&gt;Whiteboard&lt;/strong&gt;, and a &lt;strong&gt;Notes&lt;/strong&gt; app. Everything is synced in real-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Support for Multiple Programming Languages
&lt;/h3&gt;

&lt;p&gt;We engineered the workspace to be language-agnostic. By utilizing &lt;strong&gt;Monaco Editor&lt;/strong&gt; (the engine behind VS Code), Owlyn provides a native coding experience for over 20+ languages, including &lt;strong&gt;Typescript, Python, Go, Java, and C++&lt;/strong&gt;. The Workspace Sentinel (Agent 3) is specifically tuned to understand the idiomatic nuances of these languages, ensuring that whether a candidate is writing a high-performance Go routine or a clean React component, the evaluation remains context-aware and accurate.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Monitoring Mode
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Ftcyslvvkqu7vrxnk3f0k.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.amazonaws.com%2Fuploads%2Farticles%2Ftcyslvvkqu7vrxnk3f0k.png" alt="Monitoring Mode" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
For organizations, visibility is just as important as the evaluation itself. We built a &lt;strong&gt;Monitoring Dashboard&lt;/strong&gt; that serves as a real-time command center for recruitment teams. &lt;/p&gt;

&lt;p&gt;Hiring managers can join any active session as a "Silent Observer," gaining a comprehensive view of the candidate’s performance without interfering with the natural flow of the interview. The dashboard provides a live, rolling transcript, a synchronized audio waveform to visualize the conversation's cadence, and an instant alert system. If our sentinels detect a security anomaly—such as the presence of a mobile device or an unauthorized person in the frame—a flag is immediately raised on the recruiter's screen, allowing for instant intervention if necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Assistant Mode
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fogeys7w2fghtracc9u2i.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.amazonaws.com%2Fuploads%2Farticles%2Fogeys7w2fghtracc9u2i.png" alt="Assistant Mode" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
Assistant Mode transforms Owlyn into a persistent multimodal companion for everyday development. Beyond the interview, this mode operates as a &lt;strong&gt;floating widget&lt;/strong&gt; that lives alongside your IDE and terminal. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environmental Context&lt;/strong&gt;: The agent utilizes screen-share vision and your microphone to stay synchronized with your active tasks. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice-First Interaction&lt;/strong&gt;: Leveraging the &lt;strong&gt;LiveKit&lt;/strong&gt; protocol for sub-second responses, it acts as a senior pair-programmer you can talk to in real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambient Assistance&lt;/strong&gt;: Whether you're debugging a complex stack trace or architecting a new service, the Assistant provides contextual insights based on exactly what it sees on your screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  D. Practice Mode
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fddwney9f2237g19ytd9s.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.amazonaws.com%2Fuploads%2Farticles%2Fddwney9f2237g19ytd9s.png" alt="Practice Mode" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
Before entering a real technical interview session, candidates need a way to test their technical skills and get comfortable with the interview environment. We built &lt;strong&gt;Practice Mode&lt;/strong&gt; as a standalone version of the workspace where users define their own parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customizable Sessions&lt;/strong&gt;: Users input a specific topic (e.g., "React Performance" or "Distributed Systems") and set a difficulty and timer. The agent then dynamically generates a technical session based on those constraints.
--&lt;strong&gt;Multi Language Support&lt;/strong&gt;: Users can select their preferred language from the list of supported languages. This instructs the AI agent to communicate with the candidate in their preferred language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol Parity&lt;/strong&gt;: It uses the same multi-agent orchestration, audio/video streaming, and code analysis as the Enterprise mode. This ensures the candidate is getting the real experience in a private environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  5. Recruitment Management: Dashboards and Talent Pools
&lt;/h1&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.amazonaws.com%2Fuploads%2Farticles%2F7ju8rfr46yzszv4ior88.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.amazonaws.com%2Fuploads%2Farticles%2F7ju8rfr46yzszv4ior88.png" alt="Dashboard" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
Technical hiring is about more than just one interview; it is about managing many candidates effectively across an entire company. We built a management system to help teams handle their hiring process from start to finish:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Recruitment Dashboard
&lt;/h3&gt;

&lt;p&gt;This is the main control center where hiring teams manage their daily work. It shows what is happening in the company right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Track Candidates in Real-Time&lt;/strong&gt;: Managers can see which candidates are waiting in the lobby, which interviews are currently live, and which ones are finished. This helps teams stay organized during busy hiring seasons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Based Customization&lt;/strong&gt;: Recruiters can set different rules for different positions. You can choose the language the AI speaks and set how strict it should be. For example, you can make the AI a helpful guide for a junior developer or a very rigorous judge for a senior lead. You can also upload specific coding tasks for each role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human Decision Making (Hire or Decline)&lt;/strong&gt;: While the AI handles the interview, the final choice always belongs to a human. After the interview, a recruiter reviews the technical report and uses the &lt;strong&gt;Hire or Decline&lt;/strong&gt; buttons to make a final evaluation. This ensures that a real person always has the last word.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Team Collaboration
&lt;/h3&gt;

&lt;p&gt;Hiring is a team sport. We added features to help engineering leads and recruiters work together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team Management&lt;/strong&gt;: Organizations can invite multiple team members to the dashboard. This allows different people to review the same candidate report and share their feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Feedback&lt;/strong&gt;: Recruiters can leave notes on a candidate's profile for other team members to see. This helps build a complete picture of the candidate’s performance from different perspectives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Analysis and Reporting
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fwkoa4jmbydn7jv8gux96.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.amazonaws.com%2Fuploads%2Farticles%2Fwkoa4jmbydn7jv8gux96.png" alt="Analysis" width="800" height="520"&gt;&lt;/a&gt;&lt;br&gt;
Once an interview is over, the AI generates a detailed technical report. This report is the primary tool recruiters use to make their hiring decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical Radar Charts&lt;/strong&gt;: The system visualizes candidate skills (like Problem Solving, Communication, and Code Quality) on a radar chart. This makes it easy to see at a glance where a candidate is strongest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Transcripts and Code History&lt;/strong&gt;: Recruiters can read every word said during the interview and see exactly how the candidate's code evolved over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Summary&lt;/strong&gt;: The AI provides a short summary of the candidate's performance, highlighting both their strengths and their mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Talent Pool
&lt;/h3&gt;

&lt;p&gt;The Talent Pool is a central library where all candidate results are kept. It helps teams find the best talent faster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compare Skills Side-by-Side&lt;/strong&gt;: The system takes the AI's technical scores and puts them in a simple table. This lets recruiters filter candidates by their score or role, making it easy to see who performed best across hundreds of sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talent Analytics&lt;/strong&gt;: The dashboard shows overall stats for your hiring pipeline, such as the total number of candidates, the percentage of high-potential profiles, and the average score for a specific role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exporting for Teams&lt;/strong&gt;: Recruiters can export lists of elite candidates to share with other departments, ensuring that top talent is never lost in the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbiased Ranking&lt;/strong&gt;: Because the AI scores everyone using the same technical rules, the Talent Pool gives teams an objective way to rank people based on their actual coding ability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Real-Time Multimodal Pipelines
&lt;/h2&gt;

&lt;p&gt;Capturing human interaction for an autonomous agent is technically demanding. We had to solve for two main things: data shape and latency.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Video Feed&lt;/strong&gt;, we stream 1 frame per second. We found this to be the "sweet spot" for Gemini’s Vision capabilities—it’s enough to detect a phone or a change in gaze without destroying the candidate's upload bandwidth.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Audio&lt;/strong&gt;, we use 16kHz mono PCM. This is streamed up to our Java server, which then pipes it directly into the Gemini Live API via the &lt;strong&gt;Google ADK&lt;/strong&gt;. We spent a lot of time on the &lt;code&gt;audio.service.ts&lt;/code&gt; to ensure that audio chunks are small enough for low latency but large enough to maintain quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Security: The Sentinel Mode
&lt;/h2&gt;

&lt;p&gt;Integrity is non-negotiable for professional assessments. We implemented what we call &lt;strong&gt;Sentinel Mode&lt;/strong&gt; to protect the session:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;OS-Level Lockdown&lt;/strong&gt;: We use Electron's &lt;code&gt;globalShortcut&lt;/code&gt; to block navigation and &lt;code&gt;win.setContentProtection(true)&lt;/code&gt; to make the screen appear black to recording software like OBS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environmental Breach Detection&lt;/strong&gt;: We listen for "blur" events. If the candidate switches windows, it’s logged as a breach, and the agent verbally warns them to focus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision-Based Security Monitoring&lt;/strong&gt;: Since Gemini is looking at the 1fps feed, it natively detects unauthorized objects (phones, tablets) or external participants in the room.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  8. Engineering Decisions
&lt;/h2&gt;

&lt;p&gt;Every engineering project is a series of trade-offs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency vs. Accuracy&lt;/strong&gt;: We chose 1fps for the vision feed over a smoother 10fps. While less "fluid," it ensures that candidates on a standard home connection can participate without lag or video fragmentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Session Orchestration&lt;/strong&gt;: We chose to coordinate multiple agents through our Spring Boot backend rather than having the client manage separate peer-to-peer connections with 4+ different Gemini models. While this centralization adds architectural complexity, it was essential for maintaining a unified state across the Interviewer, Workspace Sentinel, and Integrity Sentinel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Context Logic Verification&lt;/strong&gt;: We leveraged Gemini’s specialized reasoning models to verify logic in real-time. This avoided the overhead of a formal execution sandbox while allowing the agent to provide feedback on implementation details as they unfold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory-First State Management&lt;/strong&gt;: We use Redis for all active session data (transcripts, flags, editor state). This sacrifices the "safety" of persistent disk writes for the sub-millisecond updates required in a live, voice-driven environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Closing Thoughts and Future Roadmap
&lt;/h2&gt;

&lt;p&gt;Building Owlyn for the &lt;strong&gt;#GeminiLiveAgentChallenge&lt;/strong&gt; allowed us to move beyond traditional AI interviews by creating a truly live and immersive workspace. By synchronizing voice and vision, we’ve enabled both high-fidelity technical assessments and a persistent assistant mode for everyday development. We see this multimodal synergy as the new standard for how technical talent is discovered, validated, and empowered in a high-stakes engineering world.&lt;/p&gt;

&lt;p&gt;Looking forward, we’re expanding the Owlyn ecosystem with the following roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team-Based Mode&lt;/strong&gt;: Moving beyond silent monitoring to allow recruiters to "jump in" to the live session at any point. This enables a hybrid workflow where a human interviewer can take over the lead from the AI for a final, high-fidelity cultural evaluation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deeper ATS Integration&lt;/strong&gt;: One-click exports to tools like Greenhouse or Lever to automate the hiring funnel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborative Whiteboarding&lt;/strong&gt;: Support for real-time collaboration between the agent and candidate on the same whiteboard canvas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building this for the &lt;strong&gt;#GeminiLiveAgentChallenge&lt;/strong&gt; has been an incredible experience, and we’re just getting started. 🦉&lt;/p&gt;

&lt;h3&gt;
  
  
  The Owlyn Team
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Code Repository
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: &lt;a href="https://github.com/A-Simie/Owlyn" rel="noopener noreferrer"&gt;https://github.com/A-Simie/Owlyn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: &lt;a href="https://github.com/Akeem1955/OwlynBackend" rel="noopener noreferrer"&gt;https://github.com/Akeem1955/OwlynBackend&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contributor&lt;/th&gt;
&lt;th&gt;GitHub Profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mosimiloluwa Adebisi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/A-Simie" rel="noopener noreferrer"&gt;@A-Simie&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Akeem Adetunji&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Akeem1955" rel="noopener noreferrer"&gt;@akeem&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adenuga Abdulrahmon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Rahmannugar" rel="noopener noreferrer"&gt;@Rahmannugar&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>geminiliveagentchallenge</category>
      <category>googleaichallenge</category>
      <category>googlecloud</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Suite 33: A business management platform for SMEs.</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Thu, 27 Nov 2025 01:26:49 +0000</pubDate>
      <link>https://dev.to/rahmannugar/suite-33-a-business-management-platform-for-smes-1gdl</link>
      <guid>https://dev.to/rahmannugar/suite-33-a-business-management-platform-for-smes-1gdl</guid>
      <description>&lt;p&gt;Running a small or medium sized business is difficult enough when operations are structured. Small and medium sized enterprises often rely on manual processes to run their operations. Sales are recorded on paper, inventory is tracked informally, payroll is handled in notebooks, and staff performance is evaluated without any structured system. These gaps often create delays, inaccuracies which is detrimental to business growth.&lt;/p&gt;

&lt;p&gt;Suite33 was created to close those gaps. It is a business management platform designed specifically for SMEs. The goal is simple. Build a digital system that reflects how small businesses already work, while making their day to day operations cleaner, faster and easier to track.&lt;/p&gt;

&lt;p&gt;Live Link - &lt;a href="https://suite33.vercel.app" rel="noopener noreferrer"&gt;Suite33&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Why Suite33 Was Built&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. Understanding the Challenges SMEs Face&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;3. Designing the Suite33 Experience&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;4. Getting Started: Onboarding and Setup&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;5. The Modules That Shape Suite33&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;6. Business Security and Roles&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;7. Lessons Learned While Building Suite33&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;8. Closing Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Suite33 Was Built
&lt;/h2&gt;

&lt;p&gt;Suite33 did not begin as a random idea. It came from firsthand experience.&lt;br&gt;
Recently, I was consulting with a business and I quickly noticed they ran their entire business activities with unorganized records. The business owner had no central view of how the business was performing. Decisions were made from instinct instead of data. It became clear that these businesses needed structure, hence Suite33.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding the Challenges SMEs Face
&lt;/h2&gt;

&lt;p&gt;Before writing a line of code, it was important to understand the real problems facing several SMEs in Nigeria.&lt;/p&gt;

&lt;p&gt;After a thorough case study, I was able to draft out some of the most common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales records disappear easily because they are written in notebooks or loose sheets.&lt;/li&gt;
&lt;li&gt;Inventory is uncertain, making it difficult to know what is available at any moment.&lt;/li&gt;
&lt;li&gt;Payroll takes too long, especially when each staff salary is calculated manually.&lt;/li&gt;
&lt;li&gt;Staff performance is subjective, with no historical score or monthly evaluation.&lt;/li&gt;
&lt;li&gt;Financial insights are unclear, since there is no unified view of revenue, spending and profit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Designing the Suite33 Experience
&lt;/h2&gt;

&lt;p&gt;I modelled Suite33 around clarity and structure. Instead of building a complex enterprise style system, the goal was to create something simple enough for daily use but powerful enough to rely on.&lt;/p&gt;

&lt;p&gt;Every screen, every form and every action follows three principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Keep the workflow familiar&lt;/strong&gt;&lt;br&gt;
If a business is used to notebooks, Suite33 should feel like a digital extension of that, not a replacement that forces new habits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reduce cognitive load&lt;/strong&gt;&lt;br&gt;
No cluttered screens, no confusing menus. Businesses should be able to understand their data at a glance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Centralize everything&lt;/strong&gt;&lt;br&gt;
Sales, expenses, inventory, payroll, staff and KPIs should all be centralized in a single dashboard application.&lt;/p&gt;

&lt;p&gt;This approach ensured that Suite33 could serve small teams without overwhelming them.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Getting Started: Onboarding and Setup
&lt;/h2&gt;

&lt;p&gt;The onboarding flow guides the business into the platform step by step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business Setup&lt;/strong&gt;&lt;br&gt;
Users begin by providing the essential details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business name&lt;/li&gt;
&lt;li&gt;Industry&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Optional Business logo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately personalizes the workspace and prepares the environment for operations.&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.amazonaws.com%2Fuploads%2Farticles%2Fa80n623nikqhnq2gv68q.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.amazonaws.com%2Fuploads%2Farticles%2Fa80n623nikqhnq2gv68q.png" alt="Business Onboarding Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inviting Staff&lt;/strong&gt;&lt;br&gt;
Admins can invite up to 10 staff members per month.&lt;br&gt;
This limit is intentional. It encourages businesses to gradually set up their team and ensures the system remains manageable during early adoption.&lt;br&gt;
Staff receive an email and join the business with their own login. Their roles determine what they can access in the platform.&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.amazonaws.com%2Fuploads%2Farticles%2Fyca75ueklokrdhbbokeo.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.amazonaws.com%2Fuploads%2Farticles%2Fyca75ueklokrdhbbokeo.png" alt="Staff Invite Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editing Profile&lt;/strong&gt;&lt;br&gt;
Every user can update their personal information. This includes updating name, avatar.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fijikzr8qfmbpv8nf6sab.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.amazonaws.com%2Fuploads%2Farticles%2Fijikzr8qfmbpv8nf6sab.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fjlfohgjl4pxvni5xbbmu.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.amazonaws.com%2Fuploads%2Farticles%2Fjlfohgjl4pxvni5xbbmu.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Modules That Shape Suite33
&lt;/h2&gt;

&lt;p&gt;Suite33 contains several core modules, each responsible for one part of the business operation. They are connected behind the scenes, which allows the dashboard to generate a complete view of business performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales Management&lt;/strong&gt;&lt;br&gt;
The sales module makes it easy for businesses to record daily sales and monitor revenue trends over time.&lt;br&gt;
With the ability to track monthly progress, visualize data export results and gain insights, SMEs finally gain clarity about how their business is performing. This transforms guesswork into informed decisions.&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.amazonaws.com%2Fuploads%2Farticles%2F0p14bzcrczhyx7ax1vz1.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.amazonaws.com%2Fuploads%2Farticles%2F0p14bzcrczhyx7ax1vz1.png" alt="Sales Image" width="799" height="451"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fh99nmpqk54ln5xjzl6tv.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.amazonaws.com%2Fuploads%2Farticles%2Fh99nmpqk54ln5xjzl6tv.png" alt="Sales Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expenditures&lt;/strong&gt;&lt;br&gt;
For a business to truly understand its profit margins, it must first understand spending.&lt;br&gt;
Suite33 allows businesses to log every expense, categorize it and review spending patterns throughout the year. Every expenditure feeds directly into the dashboard’s profit and loss calculation.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fkwcbpx3zdjwkcij436t4.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.amazonaws.com%2Fuploads%2Farticles%2Fkwcbpx3zdjwkcij436t4.png" alt="Expenditures Image" width="800" height="450"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F7cv4gykr2chowmgqi9gp.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.amazonaws.com%2Fuploads%2Farticles%2F7cv4gykr2chowmgqi9gp.png" alt="Expenditures Image" width="799" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inventory Management&lt;/strong&gt;&lt;br&gt;
Inventory issues are one of the biggest pain points for SMEs. Suite33 simplifies this process with a clean, structured layout.&lt;/p&gt;

&lt;p&gt;Businesses can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create categories&lt;/li&gt;
&lt;li&gt;Add new inventory items&lt;/li&gt;
&lt;li&gt;Track quantities&lt;/li&gt;
&lt;li&gt;Edit details as stock changes&lt;/li&gt;
&lt;li&gt;Export inventory lists&lt;/li&gt;
&lt;li&gt;View low stock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system prevents accidental deletion of categories that still contain items, protecting the integrity of the data.&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.amazonaws.com%2Fuploads%2Farticles%2F5uyw1xfmuazgd9ipyz7x.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.amazonaws.com%2Fuploads%2Farticles%2F5uyw1xfmuazgd9ipyz7x.png" alt="Inventory Image" width="800" height="451"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F94pq2fnq67jyriuvuuqf.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.amazonaws.com%2Fuploads%2Farticles%2F94pq2fnq67jyriuvuuqf.png" alt="Inventory Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payroll&lt;/strong&gt;&lt;br&gt;
Payroll is often the most repetitive and time consuming task for SMEs. Suite33 introduces a monthly payroll batch system that automates most of the work.&lt;/p&gt;

&lt;p&gt;Each month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The admin generates a batch.&lt;/li&gt;
&lt;li&gt;Staff salaries are automatically included.&lt;/li&gt;
&lt;li&gt;Admin can update staff salaries and mark each staff as paid or unpaid.&lt;/li&gt;
&lt;li&gt;When the batch is complete, it can be locked to prevent accidental changes.&lt;/li&gt;
&lt;li&gt;Staff can view their payslip privately.&lt;/li&gt;
&lt;li&gt;Admins can export the payroll to Excel or CSV.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fs77deqvf3ddeuobsbrwp.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2Fs77deqvf3ddeuobsbrwp.JPG" alt="Payroll Image" width="799" height="350"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F9oytfn73dk70oy1do5ak.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2F9oytfn73dk70oy1do5ak.JPG" alt="Payroll Image" width="800" height="334"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fukuf5llopu685r4kptat.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2Fukuf5llopu685r4kptat.JPG" alt="Payroll Image" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staff Management&lt;/strong&gt;&lt;br&gt;
Staff records are organized in one place. Admins can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new staff&lt;/li&gt;
&lt;li&gt;Remove staff&lt;/li&gt;
&lt;li&gt;Edit roles&lt;/li&gt;
&lt;li&gt;Assign departments&lt;/li&gt;
&lt;li&gt;Staff only see information relevant to them.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fiwxgpqgj1ftnmqn6i106.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.amazonaws.com%2Fuploads%2Farticles%2Fiwxgpqgj1ftnmqn6i106.png" alt="Organization Management Image" width="800" height="450"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fv0xy5vr0jnmxuu5s0mgp.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.amazonaws.com%2Fuploads%2Farticles%2Fv0xy5vr0jnmxuu5s0mgp.png" alt="Organization Management Image" width="799" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;KPI Tracking&lt;br&gt;
Performance evaluation becomes simpler with monthly KPI scoring.&lt;br&gt;
Instead of relying on memory or last minute judgments, businesses can maintain a consistent record of how each staff performs throughout the year.&lt;br&gt;
KPI scores help guide promotions, reviews and improvements.&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.amazonaws.com%2Fuploads%2Farticles%2Fm20hsrypc46a9bi098oy.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2Fm20hsrypc46a9bi098oy.JPG" alt="KPI Image" width="800" height="347"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fyu9yxy0rsirhnb53cqiv.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2Fyu9yxy0rsirhnb53cqiv.JPG" alt="KPI Image" width="799" height="347"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Frhpedbfcznc56zy84xmw.JPG" 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.amazonaws.com%2Fuploads%2Farticles%2Frhpedbfcznc56zy84xmw.JPG" alt="KPI Image" width="798" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dashboard Overview&lt;/strong&gt;&lt;br&gt;
The dashboard is the heart of Suite33.&lt;br&gt;
It brings together data from every module to give the business a complete operational overview.&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.amazonaws.com%2Fuploads%2Farticles%2Fb54607pmeppobyll11e1.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.amazonaws.com%2Fuploads%2Farticles%2Fb54607pmeppobyll11e1.png" alt="Dashboard Overview Image" width="799" height="449"&gt;&lt;/a&gt;&lt;br&gt;
The dashboard displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales summary&lt;/li&gt;
&lt;li&gt;Expenditures summary&lt;/li&gt;
&lt;li&gt;Staff count&lt;/li&gt;
&lt;li&gt;Inventory count&lt;/li&gt;
&lt;li&gt;Payroll status&lt;/li&gt;
&lt;li&gt;Profit and loss table&lt;/li&gt;
&lt;li&gt;Business identity
This allows business owners to view their financial and operational health at a glance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Business Security and Roles
&lt;/h2&gt;

&lt;p&gt;Suite33 is structured around three access levels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full access to all operations and business management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sub Admin(Assistant Admin)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support role with management access but limited payroll visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Staff&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restricted access with visibility limited to personal payslips and details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure ensures privacy, accountability and security on all fronts.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Lessons Learned While Building Suite33
&lt;/h2&gt;

&lt;p&gt;Building Suite33 revealed several important insights about SMEs and software design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. SMEs value simplicity above all else&lt;/strong&gt;&lt;br&gt;
Most small businesses do not need complex systems. They need clarity. They need something that simplifies their existing processes instead of replacing them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Onboarding is as important as the product&lt;/strong&gt;&lt;br&gt;
Businesses lose interest quickly if the first steps feel overwhelming. Refining onboarding to be quick and human centered made adoption smoother.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Accuracy is everything when dealing with money&lt;/strong&gt;&lt;br&gt;
Payroll and financial modules must leave no room for uncertainty. The smallest error can create distrust. Ensuring reliability at every stage became a priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A business cannot grow without visibility&lt;/strong&gt;&lt;br&gt;
SMEs often operate in the dark. When they finally see their numbers clearly, they make better decisions. Data presentation mattered just as much as data storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Scalability is not only about technology&lt;/strong&gt;&lt;br&gt;
It is also about designing workflows that still make sense when the business grows from 5 staff to 50 staff. Features were built to feel predictable, even when usage increases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Every role needs a different view of the system&lt;/strong&gt;&lt;br&gt;
Admins do not need the same interface as staff, and staff should never see what belongs only to administrators. Clear separation of roles became essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Suite33 is designed to give SMEs a more organized way to run their business. It replaces manual processes with a single system that tracks sales, inventory, expenses, payroll and staff performance in one place.&lt;/p&gt;

&lt;p&gt;The platform does not aim to complicate operations. Instead, it brings the structure small businesses need to work more efficiently and make informed decisions.&lt;/p&gt;

&lt;p&gt;As SMEs continue to grow, Suite33 gives them a dependable foundation to grow on.&lt;/p&gt;

</description>
      <category>businessmanagement</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Contemporary State Management and Data Operations in React</title>
      <dc:creator>Rahman Nugar</dc:creator>
      <pubDate>Thu, 16 Oct 2025 13:29:14 +0000</pubDate>
      <link>https://dev.to/rahmannugar/contemporary-state-management-and-data-operations-in-react-3kja</link>
      <guid>https://dev.to/rahmannugar/contemporary-state-management-and-data-operations-in-react-3kja</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Tools evolve, trends change.
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;React introduced developers to a new way of thinking about UI, where components react to data and user interactions in real time. It started as a simple idea: manage state locally and let the UI update automatically. But as applications grew more complex, so did the challenge of handling state that needed to live across many parts of an app.&lt;/p&gt;

&lt;p&gt;Fast forward to &lt;code&gt;currentDate()&lt;/code&gt;, and we’ve come a long way from simple &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; for state management and data fetching respectively. Today, React developers work with &lt;code&gt;Context API&lt;/code&gt;, &lt;code&gt;Zustand&lt;/code&gt;, &lt;code&gt;Redux Toolkit&lt;/code&gt;, for managing state and &lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;RTK Query&lt;/code&gt;, server side fetching(Nextjs) for querying or mutating data. Each tool solves a slightly different problem, and knowing which one to use for a particular project makes all the difference.&lt;/p&gt;

&lt;p&gt;In this article, we’ll take a detailed look at modern state management, data query and mutation in React. You’ll learn when each tool shines, how they fit together, and how to make the most of them in a modern React app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Understanding React’s Core State Model&lt;/li&gt;
&lt;li&gt;Context API: The Native Option&lt;/li&gt;
&lt;li&gt;Zustand: Lightweight Yet Scalable&lt;/li&gt;
&lt;li&gt;Redux Toolkit: Predictable and Enterprise-Ready&lt;/li&gt;
&lt;li&gt;useEffect: The Misused Hook&lt;/li&gt;
&lt;li&gt;TanStack Query&lt;/li&gt;
&lt;li&gt;RTK Query&lt;/li&gt;
&lt;li&gt;ServerSide Fetching&lt;/li&gt;
&lt;li&gt;Patterns and Best Practices&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Understanding React’s Core State Model
&lt;/h2&gt;

&lt;p&gt;Before looking at the aforementioned contemporary tools, it’s worth revisiting the basics.&lt;/p&gt;

&lt;p&gt;React’s design encourages predictable, unidirectional data flow. Each component has its own state, typically managed with hooks like &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useReducer&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Usestate&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [name, setName] = useState("Nugar");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works perfectly for local, isolated state such as toggling a modal or tracking form input.&lt;/p&gt;

&lt;p&gt;For more complex scenarios, developers often use useReducer for clearer state transitions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UseReducer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialState = { isOpen: false, modalType: null };

function reducer(state, action) {
  switch (action.type) {
    case "OPEN_MODAL": return { isOpen: true, modalType: action.payload };
    case "CLOSE_MODAL": return { isOpen: false, modalType: null };
    default: return state;
  }
}

const [state, dispatch] = useReducer(reducer, initialState);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem arises when different components need access to the same data. Manually passing props up and down the tree leads to “prop drilling,” which quickly becomes difficult to maintain. This is where global state solutions step in.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Context API: The Native Option
&lt;/h2&gt;

&lt;p&gt;The Context API is React’s built-in way to share data globally across components without prop drilling. It’s simple, dependency-free, and ideal for small applications or simple global state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// theme-context.tsx
import { createContext, useContext, useState } from "react";

const ThemeContext = createContext(null);

export const ThemeProvider = ({ children }) =&amp;gt; {
  const [theme, setTheme] = useState("light");
  const toggleTheme = () =&amp;gt; setTheme(t =&amp;gt; (t === "light" ? "dark" : "light"));

  return (
    &amp;lt;ThemeContext.Provider value={{ theme, toggleTheme }}&amp;gt;
      {children}
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
};

export const useTheme = () =&amp;gt; useContext(ThemeContext);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Then in any component:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { theme, toggleTheme } = useTheme();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Use&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small projects or simple global state&lt;/li&gt;
&lt;li&gt;Shared values like theme, language, or authentication status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can trigger re-renders across all consuming components&lt;/li&gt;
&lt;li&gt;Becomes hard to manage as the app grows&lt;/li&gt;
&lt;li&gt;Not ideal for frequent or performance-sensitive updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Context API shines for lightweight scenarios, but once your application grows or requires complex state logic, a dedicated library becomes a better fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Zustand: Lightweight Yet Scalable
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Zustand&lt;/code&gt; has become a favorite among developers for its simplicity and performance. It provides a minimal, intuitive API that lets you create global stores without providers or reducers. I frequently use Zustand nowadays for projects that require global state management due to its simplicity and ease of use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { create } from "zustand";

const useUserStore = create(set =&amp;gt; ({
  user: null,
  setUser: user =&amp;gt; set({ user }),
  logout: () =&amp;gt; set({ user: null })
}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Usage&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { user, setUser, logout } = useUserStore();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No boilerplate, no extra setup. Zustand uses shallow comparison to prevent unnecessary re-renders, keeping your app fast and efficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Developers Love It&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal API and easy learning curve&lt;/li&gt;
&lt;li&gt;Excellent TypeScript support&lt;/li&gt;
&lt;li&gt;Works great for UI and session state&lt;/li&gt;
&lt;li&gt;Supports persistence, middlewares, and subscriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small to medium projects that need global state&lt;/li&gt;
&lt;li&gt;Applications with local caching or session management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Redux Toolkit: Predictable and Enterprise-Ready
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Redux&lt;/code&gt; has been around for years, and while it used to be associated with boilerplate and verbosity, Redux Toolkit (RTK) changed that. RTK provides structured, opinionated utilities that simplify reducers, actions, and store configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit";

interface AuthState {
  user: { name: string } | null;
}

const initialState: AuthState = { user: null };

const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    login: (state, action: PayloadAction&amp;lt;{ name: string }&amp;gt;) =&amp;gt; {
      state.user = action.payload;
    },
    logout: state =&amp;gt; {
      state.user = null;
    },
  },
});

export const { login, logout } = authSlice.actions;
export const store = configureStore({ reducer: { auth: authSlice.reducer } });
export type RootState = ReturnType&amp;lt;typeof store.getState&amp;gt;;
export type AppDispatch = typeof store.dispatch;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Usage&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useSelector, useDispatch } from "react-redux";
import { login, logout, RootState } from "./store";

export default function App() {
  const user = useSelector((state: RootState) =&amp;gt; state.auth.user);
  const dispatch = useDispatch();

  return user ? (
    &amp;lt;button onClick={() =&amp;gt; dispatch(logout())}&amp;gt;Logout&amp;lt;/button&amp;gt;
  ) : (
    &amp;lt;button onClick={() =&amp;gt; dispatch(login({ name: "Rahman" }))}&amp;gt;Login&amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. useEffect: The Misused Hook
&lt;/h2&gt;

&lt;p&gt;Before exploring modern data-fetching solutions, it’s important to talk about &lt;code&gt;useEffect&lt;/code&gt;, a hook that was never designed for fetching or mutating data, yet became the default tool for it in most React apps.&lt;/p&gt;

&lt;p&gt;When React Hooks were introduced, &lt;code&gt;useEffect&lt;/code&gt; filled an essential gap in functional components. It allowed developers to perform side effects; actions that happen outside React’s rendering cycle. These effects include subscribing to events, updating the document title, managing timers, or synchronizing state with browser APIs.&lt;/p&gt;

&lt;p&gt;In simple terms, &lt;code&gt;useEffect&lt;/code&gt; lets your component “reach outside” React’s pure rendering world and interact with the environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  document.title = `Hello, ${userName}`;
}, [userName]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a perfect use case: the effect reacts to changes in &lt;code&gt;userName&lt;/code&gt; and synchronizes the browser title accordingly.&lt;/p&gt;

&lt;p&gt;However, over time, developers began using &lt;code&gt;useEffect&lt;/code&gt; for something it wasn’t built for — data fetching and mutation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  async function loadUser() {
    const res = await fetch("/api/user");
    const data = await res.json();
    setUser(data);
  }
  loadUser();
}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this pattern works for small applications, it quickly becomes fragile as your app grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why useEffect Is a Poor Fit for Data Operations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It Easily Re-Triggers Requests&lt;/li&gt;
&lt;li&gt;It Doesn’t Handle Caching or Syncing&lt;/li&gt;
&lt;li&gt;It Causes Race Conditions&lt;/li&gt;
&lt;li&gt;It Adds Manual Overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems have even caused real-world incidents. Recently, Cloudflare experienced a large-scale performance issue that stemmed from an application repeatedly firing API calls due to misused React effects. The component’s re-renders multiplied outgoing requests, consuming massive internal bandwidth. The problem wasn’t React, it was a hook used for something beyond its purpose.&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.amazonaws.com%2Fuploads%2Farticles%2F2p1phpaz7dh8bexzer5h.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F2p1phpaz7dh8bexzer5h.jpg" alt="Cloudflare Blogpost on useEfffect" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What &lt;code&gt;useEffect&lt;/code&gt; Was Actually Meant For&lt;/strong&gt;&lt;br&gt;
The React team’s intention for useEffect has always been clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up subscriptions or event listeners&lt;/li&gt;
&lt;li&gt;Managing timers, intervals, or animations&lt;/li&gt;
&lt;li&gt;Interacting with browser or third-party APIs&lt;/li&gt;
&lt;li&gt;Cleaning up resources when a component unmounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are side effects, they are interactions that depend on or modify something outside of React’s data flow.&lt;/p&gt;

&lt;p&gt;Fetching data, however, is a data flow operation, not a side effect. It belongs in a layer that manages caching, background refetching, synchronization, and invalidation. That’s far beyond what useEffect was built to handle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern Tools Do It Differently&lt;/strong&gt;&lt;br&gt;
Libraries like React Query, RTK Query, and frameworks like Next.js have redefined how React handles data operations.&lt;/p&gt;

&lt;p&gt;These tools aren’t built on top of &lt;code&gt;useEffect&lt;/code&gt;. Instead, they use dedicated data layers that integrate deeply with React’s rendering engine, concurrent features, and Suspense. They know when to fetch, how to cache, and when to revalidate, all without you manually orchestrating it.&lt;/p&gt;

&lt;p&gt;In modern React, &lt;code&gt;useEffect&lt;/code&gt; should be reserved for what it was meant to do: handling external side effects at the component level. For data fetching, the ecosystem has evolved, and the right tools now exist to handle that responsibility cleanly and efficiently.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. TanStack Query: Data Fetching Made Declarative
&lt;/h2&gt;

&lt;p&gt;TanStack Query formerly known as React Query introduced a fundamental shift in how developers think about server data. Instead of manually managing loading, error, and caching logic, React Query abstracts those concerns behind a declarative API. It treats data as a cache that stays in sync with the server, not as temporary state inside your component.&lt;/p&gt;

&lt;p&gt;TanStack Query revolves around two main concepts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Queries for reading data (fetching)&lt;/li&gt;
&lt;li&gt;Mutations for writing or updating data (creating, editing, deleting)
Both are managed within a powerful caching layer that automatically keeps your UI in sync with your backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Query Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery } from "@tanstack/react-query";

function UserProfile() {
  const { data, isLoading, isError } = useQuery({
    queryKey: ["user"],
    queryFn: async () =&amp;gt; {
      const res = await fetch("/api/user");
      if (!res.ok) throw new Error("Failed to fetch user");
      return res.json();
    },
  });

  if (isLoading) return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  if (isError) return &amp;lt;p&amp;gt;Something went wrong.&amp;lt;/p&amp;gt;;

  return &amp;lt;h2&amp;gt;Welcome back, {data.name}&amp;lt;/h2&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hook handles fetching, caching, and background refetching automatically. When the &lt;code&gt;queryKey&lt;/code&gt; changes, React Query knows it’s a different dataset and fetches accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutation Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useMutation, useQueryClient } from "@tanstack/react-query";
function UpdateUser() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: async (user) =&amp;gt; {
      const res = await fetch("/api/user", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(user),
      });
      if (!res.ok) throw new Error("Failed to update user");
      return res.json();
    },
    onSuccess: () =&amp;gt; {
      // Automatically refresh user data
      queryClient.invalidateQueries(["user"]);
    },
  });

  const handleUpdate = () =&amp;gt; {
    mutation.mutate({ name: "Rahman Nugar" });
  };

  return (
    &amp;lt;button onClick={handleUpdate} disabled={mutation.isPending}&amp;gt;
      {mutation.isPending ? "Updating..." : "Update Profile"}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TanStack Query handles retries, optimistic updates, and cache synchronization under the hood, freeing you from manually tracking loading or error states.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries cache data and refresh it only when needed.&lt;/li&gt;
&lt;li&gt;Mutations update the server and sync local caches automatically.&lt;/li&gt;
&lt;li&gt;Background updates ensure your data stays fresh without blocking UI rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. RTK Query: Redux-Powered Data Management
&lt;/h2&gt;

&lt;p&gt;While TanStack Query is a standalone solution, RTK Query extends the Redux Toolkit ecosystem with an integrated data-fetching layer. It’s ideal if you already use Redux for state management but want modern, efficient data handling without extra libraries.&lt;/p&gt;

&lt;p&gt;RTK Query provides similar declarative querying and mutation features but ties them directly into your Redux store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up RTK Query&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const userApi = createApi({
  reducerPath: "userApi",
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: (builder) =&amp;gt; ({
    getUser: builder.query({
      query: () =&amp;gt; "/user",
    }),
    updateUser: builder.mutation({
      query: (user) =&amp;gt; ({
        url: "/user",
        method: "PUT",
        body: user,
      }),
    }),
  }),
});

export const { useGetUserQuery, useUpdateUserMutation } = userApi;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Integrate it into your Redux store:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { configureStore } from "@reduxjs/toolkit";
import { userApi } from "./userApi";

export const store = configureStore({
  reducer: {
    [userApi.reducerPath]: userApi.reducer,
  },
  middleware: (getDefaultMiddleware) =&amp;gt;
    getDefaultMiddleware().concat(userApi.middleware),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Using Queries and Mutations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function User() {
  const { data, isLoading } = useGetUserQuery();
  const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation();

  if (isLoading) return &amp;lt;p&amp;gt;Loading user...&amp;lt;/p&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{data.name}&amp;lt;/h2&amp;gt;
      &amp;lt;button
        onClick={() =&amp;gt; updateUser({ name: "Rahman Nugar" })}
        disabled={isUpdating}
      &amp;gt;
        {isUpdating ? "Updating..." : "Update"}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RTK Query automatically caches responses, invalidates old data after mutations, and deduplicates concurrent requests while keeping data in Redux for predictable debugging and inspection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Use RTK Query&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re already using Redux Toolkit.&lt;/li&gt;
&lt;li&gt;You want a single source of truth for both app and server state.&lt;/li&gt;
&lt;li&gt;You need integrated caching and request lifecycle management.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. ServerSide Fetching(Nextjs)
&lt;/h2&gt;

&lt;p&gt;With frameworks like Next.js, data fetching moved beyond the client. Instead of fetching data after rendering, Next.js introduced ways to prefetch data on the server through &lt;code&gt;getServerSideProps&lt;/code&gt;, &lt;code&gt;getStaticProps&lt;/code&gt;, and, more recently, the Server Components model.&lt;/p&gt;

&lt;p&gt;Server fetching shifts the data-fetching responsibility to the server layer, improving performance, SEO, and initial load times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example with Server Components&lt;/strong&gt;&lt;br&gt;
In the App Router (&lt;code&gt;app/&lt;/code&gt; directory), you can fetch data directly on the server without using useEffect or any client-side hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/page.tsx
async function getUser() {
  const res = await fetch("https://api.example.com/user", {
    cache: "no-store",
  });
  return res.json();
}

export default async function Page() {
  const user = await getUser();

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Welcome, {user.name}&amp;lt;/h1&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mutations in Next.js&lt;/strong&gt;&lt;br&gt;
With the new Server Actions, Next.js now supports secure, server-side mutations as well:&lt;br&gt;
&lt;/p&gt;

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

export async function updateUser(data: { name: string }) {
  await fetch("https://api.example.com/user", {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These actions can be directly called from client components using forms or event handlers, eliminating API route overhead while maintaining security and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Use&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apps with strong SEO needs or public-facing content&lt;/li&gt;
&lt;li&gt;Projects that rely on fast, server-rendered pages&lt;/li&gt;
&lt;li&gt;Scenarios where data privacy and server security matter&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Patterns and Best Practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Separate UI State from Server State&lt;/strong&gt;&lt;br&gt;
Keep client-side UI logic (like modals or form inputs) separate from server-synced data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use useEffect Only for True Side Effects&lt;/strong&gt;&lt;br&gt;
Reserve it for browser interactions, subscriptions, or third-party integrations not data fetching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Leverage Caching Layers&lt;/strong&gt;&lt;br&gt;
Tools like React Query and RTK Query prevent unnecessary requests and ensure consistent data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Server-First Approach&lt;/strong&gt;&lt;br&gt;
In frameworks like Next.js, fetch data server-side whenever possible for better performance and SEO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Handle Mutations with Invalidation&lt;/strong&gt;&lt;br&gt;
Always invalidate or update cached queries after a successful mutation to keep UI and server data aligned.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;State management and data operations are essential parts of building efficient and scalable React applications. While React’s built-in tools like Context and hooks such as useState or useReducer can handle smaller projects, more complex applications often need advanced solutions to manage global state, server state, and API interactions efficiently.&lt;/p&gt;

&lt;p&gt;For state management, libraries such as Redux Toolkit, Zustand provide robust and predictable ways to manage application-wide state. They help keep your logic organized and predictable, especially when multiple components need access to shared data or when actions in one part of the app affect another.&lt;/p&gt;

&lt;p&gt;When it comes to data fetching and mutations, tools like Tanstack Query and RTK Query simplify handling asynchronous operations, caching, and synchronization between the client and server.&lt;/p&gt;

&lt;p&gt;There are, of course, many other tools and technologies that address state management and data operations in React for example, libraries like Jotai, MobX, SWR, or even broader technologies like GraphQL that redefine how the client side communicate with the server side.&lt;/p&gt;

&lt;p&gt;That being said, the tools covered here represent the most widely adopted solutions in modern React development and the ones I’ve personally worked with in real-world projects to deliver consistent, performant, and maintainable applications.&lt;/p&gt;

</description>
      <category>react</category>
      <category>useeffect</category>
      <category>webdev</category>
      <category>redux</category>
    </item>
  </channel>
</rss>
