<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[DotNet Full Stack Dev]]></title><description><![CDATA[Become 1% better at .NET Full Stack development every day.
Master the art of building scalable applications by consistently honing your skills.
Through small, daily improvements, unlock your potential to create robust, high-performance systems. ]]></description><link>https://dotnetfullstackdev.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!t7wb!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37b06a29-ed02-4a54-a1ab-9ffa364e322f_148x148.png</url><title>DotNet Full Stack Dev</title><link>https://dotnetfullstackdev.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 30 Jun 2026 07:37:33 GMT</lastBuildDate><atom:link href="https://dotnetfullstackdev.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[DotNet Full Stack Dev]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[dotnetfullstackdev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[dotnetfullstackdev@substack.com]]></itunes:email><itunes:name><![CDATA[DotNet Full Stack Dev]]></itunes:name></itunes:owner><itunes:author><![CDATA[DotNet Full Stack Dev]]></itunes:author><googleplay:owner><![CDATA[dotnetfullstackdev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[dotnetfullstackdev@substack.com]]></googleplay:email><googleplay:author><![CDATA[DotNet Full Stack Dev]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[I Thought foreach Was Enough Until My Code Started Waiting… and Waiting… and Waiting]]></title><description><![CDATA[Early in my .NET career, I had a simple belief:]]></description><link>https://dotnetfullstackdev.substack.com/p/i-thought-foreach-was-enough-until</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/i-thought-foreach-was-enough-until</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Fri, 17 Apr 2026 00:00:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!s4v1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Early in my .NET career, I had a simple belief:</p><p>If I have a collection, I loop it.</p><pre><code>foreach (var item in items)
{
    Process(item);
}</code></pre><p>It was clean.<br><br>Predictable.<br><br>Easy to reason about.</p><p>And for a long time&#8230; it was enough.</p><p>Until one day, it wasn&#8217;t.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s4v1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s4v1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 424w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 848w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s4v1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg" width="1024" height="721" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:721,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mastering Asynchronous Programming, Concurrency, and Parallelism in C# | by Pavan pitthdiya | Medium&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Mastering Asynchronous Programming, Concurrency, and Parallelism in C# | by Pavan pitthdiya | Medium" title="Mastering Asynchronous Programming, Concurrency, and Parallelism in C# | by Pavan pitthdiya | Medium" srcset="https://substackcdn.com/image/fetch/$s_!s4v1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 424w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 848w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!s4v1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c3207ad-ac5b-4e1a-b688-1cd5cafeb886_1024x721.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a><br><br>The First Time &#8220;Correct&#8221; Code Felt Slow</h2><p>I remember working on a feature that processed a list of files.</p><p>Nothing fancy:</p><ul><li><p>read file<br></p></li><li><p>transform data<br></p></li><li><p>save output<br></p></li></ul><p>The code looked fine.</p><p>But when the dataset grew, something changed.</p><p>It didn&#8217;t break.<br><br>It just&#8230; took forever.</p><p>That&#8217;s when I first felt the difference between:</p><blockquote><p><strong>code that works</strong><br><br>and<br><br><strong>code that works under pressure</strong></p></blockquote><div><hr></div><h2>The Naive Thought: &#8220;Let&#8217;s Make It Parallel&#8221;</h2><p>The obvious idea came next:</p><blockquote><p>&#8220;Why not process multiple items at once?&#8221;</p></blockquote><p>That&#8217;s when I discovered:</p><pre><code>Parallel.ForEach(items, item =&gt;
{
    Process(item);
});</code></pre><p>And it felt like magic.</p><p>The same logic.<br><br>Faster execution.</p><p>Problem solved.</p><p>At least, that&#8217;s what I thought.</p><div><hr></div><h2>When Parallelism Started Breaking Things</h2><p>Very quickly, subtle issues appeared:</p><ul><li><p>Random exceptions<br></p></li><li><p>Data corruption<br></p></li><li><p>Race conditions<br></p></li><li><p>Inconsistent results<br></p></li></ul><p>The code didn&#8217;t look dangerous.</p><p>But it <em>was</em>.</p><p>Because I had unknowingly crossed a boundary:</p><blockquote><p>I moved from <strong>sequential thinking</strong> to <strong>concurrent thinking</strong><br><br>without changing how I designed my code.</p></blockquote><p>That&#8217;s when I realized something uncomfortable:</p><blockquote><p>Parallelism is not just about speed.<br><br>It&#8217;s about <strong>control</strong>.</p></blockquote><div><hr></div><h2>Why <code>foreach</code> Exists (and Why It Still Matters)</h2><p>Let&#8217;s go back for a second.</p><p><code>foreach</code> is not &#8220;basic&#8221;.<br><br>It&#8217;s <strong>safe by design</strong>.</p><pre><code>foreach (var item in items)
{
    Process(item);
}</code></pre><p>It guarantees:</p><ul><li><p>one item at a time<br></p></li><li><p>predictable order<br></p></li><li><p>no shared state conflicts (by default)<br></p></li></ul><p>It&#8217;s slow for large workloads, yes.<br><br>But it&#8217;s <strong>calm</strong>.</p><p>And calm code is easy to trust.</p><div><hr></div><h2>What <code>Parallel.ForEach</code> Really Introduced</h2><p><code>Parallel.ForEach</code> didn&#8217;t just introduce speed.</p><p>It introduced:</p><ul><li><p>multiple threads<br></p></li><li><p>non-deterministic execution order<br></p></li><li><p>shared state risks<br></p></li><li><p>scheduling complexity<br></p></li></ul><pre><code>Parallel.ForEach(items, item =&gt;
{
    Process(item);
});</code></pre><p>Now:</p><ul><li><p>items may run simultaneously<br></p></li><li><p>order is not guaranteed<br></p></li><li><p>multiple threads can touch the same data<br></p></li></ul><p>This is where design begins to matter more than syntax.</p><div><hr></div><h2>The First Real Problem: Shared State</h2><p>Here&#8217;s a classic mistake I made:</p><pre><code>int total = 0;

Parallel.ForEach(items, item =&gt;
{
    total += item.Value;
});</code></pre><p>Looks innocent.</p><p>But it&#8217;s broken.</p><p>Multiple threads updating <code>total</code> at the same time leads to:</p><ul><li><p>lost updates<br></p></li><li><p>incorrect results<br></p></li></ul><p>This is where I first encountered the need for <strong>thread-safe constructs</strong>.</p><div><hr></div><h2>Enter <code>ConcurrentDictionary</code> &#8212; The Controlled Shared State</h2><p>When multiple threads need to share data, normal collections fail.</p><p>That&#8217;s where <code>ConcurrentDictionary</code> comes in.</p><pre><code>var dict = new ConcurrentDictionary&lt;int, string&gt;();

Parallel.ForEach(items, item =&gt;
{
    dict.TryAdd(item.Id, item.Name);
});</code></pre><p>Why this works:</p><ul><li><p>internal locking or lock-free strategies<br></p></li><li><p>atomic operations<br></p></li><li><p>safe concurrent access<br></p></li></ul><p>But here&#8217;s the deeper realization:</p><blockquote><p>We didn&#8217;t just add a new class.<br><br>We added <strong>discipline to shared data</strong>.</p></blockquote><div><hr></div><h2>But Even That Wasn&#8217;t Enough</h2><p>At some point, I ran into a different problem.</p><p>Not just:</p><ul><li><p>&#8220;how do I process items faster?&#8221;<br></p></li></ul><p>But:</p><ul><li><p>&#8220;how do I coordinate producers and consumers?&#8221;<br></p></li></ul><p>Because now:</p><ul><li><p>one part of the system generates work<br></p></li><li><p>another part processes it<br></p></li></ul><p>And they don&#8217;t move at the same speed.</p><p>That&#8217;s when <code>BlockingCollection</code> entered my world.</p><div><hr></div><h2><code>BlockingCollection</code> &#8212; When Flow Matters More Than Speed</h2><p><code>BlockingCollection</code> is not about parallel loops.</p><p>It&#8217;s about <strong>controlled pipelines</strong>.</p><pre><code>var collection = new BlockingCollection&lt;int&gt;();

// Producer
Task.Run(() =&gt;
{
    foreach (var item in items)
    {
        collection.Add(item);
    }
    collection.CompleteAdding();
});

// Consumer
foreach (var item in collection.GetConsumingEnumerable())
{
    Process(item);
}</code></pre><p>What changed here?</p><ul><li><p>producer and consumer are decoupled<br></p></li><li><p>consumer waits when no data is available<br></p></li><li><p>producer slows down if buffer is full<br></p></li></ul><p>This is not just concurrency.</p><p>This is <strong>flow control</strong>.</p><div><hr></div><h2>That&#8217;s When It Clicked for Me</h2><p>All these constructs are not random.</p><p>They exist because problems evolved.</p><p>Let&#8217;s map that evolution:</p><div><hr></div><h2>The &#8220;Family&#8221; (Once It Finally Made Sense)</h2><h3>&#128994; <code>foreach</code></h3><ul><li><p>simple iteration<br></p></li><li><p>single-threaded<br></p></li><li><p>predictable<br></p></li><li><p>safe<br></p></li></ul><p>Used when:</p><ul><li><p>workload is small<br></p></li><li><p>order matters<br></p></li><li><p>simplicity is priority<br></p></li></ul><div><hr></div><h3>&#128993; <code>Parallel.ForEach</code></h3><ul><li><p>multi-threaded processing<br></p></li><li><p>CPU utilization<br></p></li><li><p>faster execution<br></p></li></ul><p>Used when:</p><ul><li><p>work is independent<br></p></li><li><p>no shared state<br></p></li><li><p>CPU-bound tasks<br></p></li></ul><div><hr></div><h3>&#128309; <code>ConcurrentDictionary</code> (and other concurrent collections)</h3><ul><li><p>safe shared state<br></p></li><li><p>atomic operations<br></p></li></ul><p>Used when:</p><ul><li><p>threads must share data<br></p></li><li><p>consistency matters<br></p></li></ul><p>Also part of this group:</p><ul><li><p><code>ConcurrentQueue</code><br></p></li><li><p><code>ConcurrentBag</code><br></p></li><li><p><code>ConcurrentStack</code><br></p></li></ul><div><hr></div><h3>&#128995; <code>BlockingCollection</code></h3><ul><li><p>producer-consumer coordination<br></p></li><li><p>backpressure handling<br></p></li></ul><p>Used when:</p><ul><li><p>pipeline systems<br></p></li><li><p>ingestion/processing flows<br></p></li><li><p>uneven workloads<br></p></li></ul><div><hr></div><h2>And It Still Didn&#8217;t End There&#8230;</h2><p>Once I went deeper, I found more tools in this ecosystem:</p><div><hr></div><h3>&#128310; <code>Task.WhenAll</code> (async parallelism)</h3><pre><code>await Task.WhenAll(items.Select(item =&gt; ProcessAsync(item)));</code></pre><p>This is:</p><ul><li><p>asynchronous parallelism<br></p></li><li><p>better for IO-bound tasks<br></p></li></ul><p>Not the same as <code>Parallel.ForEach</code>.</p><div><hr></div><h3>&#128311; <code>Parallel LINQ (PLINQ)</code></h3><pre><code>var results = items
    .AsParallel()
    .Select(x =&gt; Process(x))
    .ToList();</code></pre><p>A more declarative way of parallelism.</p><div><hr></div><h3>&#128310; Channels (<code>System.Threading.Channels</code>)</h3><p>Modern alternative to <code>BlockingCollection</code>.</p><p>More control.<br><br>More performance.</p><div><hr></div><h3>&#128311; TPL Dataflow (advanced pipelines)</h3><p>For complex systems:</p><ul><li><p>multi-stage processing<br></p></li><li><p>batching<br></p></li><li><p>throttling<br></p></li></ul><div><hr></div><h2>So&#8230; Why So Many?</h2><p>This question bothered me for a long time:</p><blockquote><p>&#8220;Why doesn&#8217;t .NET just give one way to do this?&#8221;</p></blockquote><p>Because the problem is not one-dimensional.</p><p>Each tool solves a <strong>different kind of pain</strong></p><div><hr></div><h2>The Real Answer (The One That Stayed With Me)</h2><p>This is what finally made everything feel calm:</p><blockquote><p><strong>Concurrency is not a feature.<br><br>It&#8217;s a set of trade-offs.</strong></p></blockquote><p>Every new construct exists because:</p><ul><li><p>the previous one was not enough<br></p></li><li><p>or was too dangerous in certain situations<br></p></li></ul><div><hr></div><h2>What Changed in My Thinking</h2><p>I stopped asking:</p><blockquote><p>&#8220;Which one is better?&#8221;</p></blockquote><p>And started asking:</p><blockquote><p>&#8220;What problem am I actually solving?&#8221;</p></blockquote><p>Because:</p><ul><li><p>using <code>Parallel.ForEach</code> for IO work is wrong<br></p></li><li><p>using <code>Task.WhenAll</code> for CPU-heavy loops is inefficient<br></p></li><li><p>using shared state without concurrent collections is dangerous<br></p></li><li><p>using concurrency when not needed is unnecessary complexity<br></p></li></ul><div><hr></div><h2>Final Thought</h2><p>At first, this ecosystem feels overwhelming.</p><p>Too many options.<br><br>Too many APIs.<br><br>Too many patterns.</p><p>But over time, something shifts.</p><p>You stop seeing:</p><ul><li><p>different classes<br></p></li></ul><p>And start seeing:</p><ul><li><p>different responsibilities<br></p></li></ul><p>And that&#8217;s when everything becomes simpler.</p><div><hr></div><h2>If You Remember One Thing</h2><blockquote><p><strong>Start simple.<br><br>Add concurrency only when pain demands it.<br><br>And when you add it, choose the tool that matches the problem &#8212; not the trend.</strong></p></blockquote><p>That&#8217;s how this evolution makes sense.</p>]]></content:encoded></item><item><title><![CDATA[The Modern Developer Is Not “Just a Coder”]]></title><description><![CDATA[Why a strong developer must think like a Debugger, Business Analyst, QA, and Release Expert]]></description><link>https://dotnetfullstackdev.substack.com/p/the-modern-developer-is-not-just</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/the-modern-developer-is-not-just</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sat, 14 Mar 2026 01:27:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fm_3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most developers lose years of growth because they believe one lie:</p><blockquote><p>&#8220;My job is to write code. Testing is QA&#8217;s job. Requirements are BA&#8217;s job. Release is DevOps&#8217; job.&#8221;</p></blockquote><p>That mindset creates average developers.</p><p>High-value developers don&#8217;t work like that. They understand one simple reality:</p><p><strong>Software is only valuable when it runs correctly in production and solves the right business problem.</strong></p><p>To do that, a developer must wear four hats&#8212;whether the org admits it or not:</p><ol><li><p>Debugger</p></li><li><p>Business Analyst</p></li><li><p>QA (Quality mindset)</p></li><li><p>Release Expert (delivery mindset)</p></li></ol><p>This article explains <strong>why</strong>, and how you can build these skills without becoming &#8220;everything everywhere.&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fm_3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fm_3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fm_3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3824958,&quot;alt&quot;:&quot;https://www.youtube.com/@DotNetFullstackDev&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/190900558?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="https://www.youtube.com/@DotNetFullstackDev" title="https://www.youtube.com/@DotNetFullstackDev" srcset="https://substackcdn.com/image/fetch/$s_!fm_3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fm_3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a180ec-e4d2-4ac7-a837-a5df53b73dd5_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://www.youtube.com/@DotNetFullstackDev</figcaption></figure></div><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><div><hr></div><h2>1) Developer as a Debugger</h2><h3>Because production doesn&#8217;t care about your intentions</h3><p>Anyone can write code that compiles.<br>A strong developer writes code that survives reality.</p><p>Reality includes:</p><ul><li><p>messy data</p></li><li><p>unexpected user behavior</p></li><li><p>slow networks</p></li><li><p>concurrency issues</p></li><li><p>timeouts</p></li><li><p>nulls</p></li><li><p>partial failures</p></li><li><p>race conditions</p></li><li><p>bad deployments</p></li><li><p>hidden dependencies</p></li></ul><p>When things break, the developer who can <strong>debug fast</strong> becomes the most valuable person in the room.</p><h3>What debugging really means (not just breakpoints)</h3><p>A mature developer uses a layered approach:</p><p><strong>A) Reproduce the problem</strong></p><ul><li><p>&#8220;When exactly does it happen?&#8221;</p></li><li><p>&#8220;Is it consistent or random?&#8221;</p></li><li><p>&#8220;Is it environment-specific (prod vs staging)?&#8221;</p></li><li><p>&#8220;What input triggers it?&#8221;</p></li></ul><p><strong>B) Reduce the scope</strong></p><ul><li><p>isolate the smallest set of conditions to trigger it</p></li><li><p>remove noise until the problem becomes obvious</p></li></ul><p><strong>C) Observe before changing</strong></p><ul><li><p>logs</p></li><li><p>metrics</p></li><li><p>traces</p></li><li><p>correlation IDs</p></li><li><p>database queries</p></li><li><p>upstream/downstream service behavior</p></li></ul><p><strong>D) Hypothesis-driven debugging</strong><br>Not &#8220;try random fixes.&#8221;<br>Instead:</p><ul><li><p>form a hypothesis</p></li><li><p>test it quickly</p></li><li><p>confirm/deny with evidence</p></li><li><p>move to next hypothesis</p></li></ul><h3>Mini example (real-life)</h3><p>Bug report: &#8220;Users say checkout fails sometimes.&#8221;</p><p>Weak developer:</p><ul><li><p>adds a try/catch</p></li><li><p>retries randomly</p></li><li><p>ships and prays</p></li></ul><p>Strong developer:</p><ul><li><p>checks payment gateway logs</p></li><li><p>correlates request IDs</p></li><li><p>sees timeouts at peak hours</p></li><li><p>identifies missing timeout policy + circuit breaker</p></li><li><p>adds proper resilience + observability</p></li><li><p>writes a regression test</p></li></ul><p><strong>Debugging is not fixing. Debugging is investigation.</strong></p><div><hr></div><h2>2) Developer as a Business Analyst</h2><h3>Because building the wrong thing perfectly is still failure</h3><p>Many project disasters are not &#8220;technical failures.&#8221;</p><p>They are <strong>misunderstanding failures</strong>.</p><p>A developer who thinks like a BA asks:</p><ul><li><p>&#8220;Why are we building this?&#8221;</p></li><li><p>&#8220;Who benefits?&#8221;</p></li><li><p>&#8220;What does success look like?&#8221;</p></li><li><p>&#8220;What are the edge cases?&#8221;</p></li><li><p>&#8220;What happens when input is missing?&#8221;</p></li><li><p>&#8220;What should happen when a dependency is down?&#8221;</p></li></ul><h3>The BA mindset developers need</h3><p><strong>A) Translate business language into system behavior</strong><br>Business says: &#8220;Approve loans faster.&#8221;<br>System reality:</p><ul><li><p>what data is required?</p></li><li><p>what validations?</p></li><li><p>what rules?</p></li><li><p>what exceptions?</p></li><li><p>what audit logs?</p></li><li><p>what SLA?</p></li></ul><p><strong>B) Clarify ambiguity early</strong><br>If requirements have multiple interpretations, don&#8217;t code. Clarify.</p><p><strong>C) Confirm with examples</strong><br>Instead of arguing, ask:</p><ul><li><p>&#8220;Can you give 3 sample scenarios?&#8221;</p></li><li><p>&#8220;What should happen in each?&#8221;<br>Then you convert it to acceptance criteria.</p></li></ul><h3>The biggest BA habit: &#8220;Definition of Done&#8221;</h3><p>A good developer doesn&#8217;t start coding without:</p><ul><li><p>expected input</p></li><li><p>expected output</p></li><li><p>error cases</p></li><li><p>success criteria</p></li><li><p>non-functional expectations (performance, security)</p></li></ul><p>That&#8217;s business analysis inside engineering.</p><div><hr></div><h2>3) Developer as a QA</h2><h3>Because quality is not a department &#8212; it&#8217;s a mindset</h3><p>QA is not only about &#8220;finding bugs.&#8221;</p><p>QA is about <strong>protecting user trust</strong>.</p><p>A developer with QA mindset thinks:</p><ul><li><p>&#8220;How can this fail?&#8221;</p></li><li><p>&#8220;How can a user break this?&#8221;</p></li><li><p>&#8220;What assumptions am I making?&#8221;</p></li><li><p>&#8220;What happens if dependency returns garbage?&#8221;</p></li><li><p>&#8220;What happens under load?&#8221;</p></li><li><p>&#8220;What if the clock/timezone changes?&#8221;</p></li><li><p>&#8220;What if the user double-clicks 5 times?&#8221;</p></li></ul><h3>The QA toolbox a developer must own</h3><p>You don&#8217;t need to become a full-time tester. But you must cover quality basics:</p><p><strong>A) Unit tests</strong></p><ul><li><p>validate logic</p></li><li><p>enforce contracts</p></li></ul><p><strong>B) Integration tests</strong></p><ul><li><p>validate database calls</p></li><li><p>validate external service interactions</p></li><li><p>validate serialization/deserialization</p></li></ul><p><strong>C) Contract tests (if microservices)</strong></p><ul><li><p>prevent breaking API changes</p></li></ul><p><strong>D) Regression tests</strong></p><ul><li><p>every serious bug gets a test so it never returns</p></li></ul><p><strong>E) Exploratory testing mindset</strong><br>Before shipping:</p><ul><li><p>try weird inputs</p></li><li><p>try large payloads</p></li><li><p>try slow network simulation (where possible)</p></li><li><p>try &#8220;unhappy paths&#8221; intentionally</p></li></ul><h3>One powerful rule I teach</h3><p><strong>If you fixed a bug and didn&#8217;t add a test, you didn&#8217;t really fix it.<br>You just postponed it.</strong></p><div><hr></div><h2>4) Developer as a Release Expert</h2><h3>Because software doesn&#8217;t matter until it&#8217;s deployed correctly</h3><p>Many developers treat release like a ceremony:<br>&#8220;DevOps will handle.&#8221;</p><p>But production failures often come from:</p><ul><li><p>wrong configuration</p></li><li><p>missing environment variables</p></li><li><p>database migration mismatch</p></li><li><p>connection strings wrong</p></li><li><p>secrets not set</p></li><li><p>feature flags misused</p></li><li><p>backward-incompatible changes</p></li><li><p>no rollback plan</p></li></ul><p>A release-aware developer prevents these.</p><h3>Release mindset essentials</h3><p><strong>A) Know your deployment flow</strong><br>Even if you don&#8217;t run it, understand:</p><ul><li><p>build pipeline stages</p></li><li><p>artifact creation</p></li><li><p>environment promotions</p></li><li><p>approvals</p></li><li><p>release notes</p></li></ul><p><strong>B) Config discipline</strong></p><ul><li><p>Never hardcode environment-specific values</p></li><li><p>Use proper configuration providers</p></li><li><p>Use secrets management</p></li></ul><p><strong>C) Backward compatibility</strong><br>If you deploy services independently:</p><ul><li><p>database changes must be additive first</p></li><li><p>APIs must remain compatible</p></li><li><p>versioning must be planned</p></li></ul><p><strong>D) Rollback readiness</strong><br>Every release should answer:</p><ul><li><p>&#8220;If it fails, how do we rollback?&#8221;</p></li><li><p>&#8220;What data changes are irreversible?&#8221;</p></li><li><p>&#8220;Do we have feature flags?&#8221;</p></li></ul><h3>The difference between a coder and an engineer</h3><p>Coder: &#8220;It works on my machine.&#8221;<br>Engineer: &#8220;It will survive production, upgrades, failures, and rollback.&#8221;</p><div><hr></div><h2>How These Four Hats Work Together (the real picture)</h2><p>When you combine these skills, you start shipping like a mature engineer:</p><ul><li><p>BA mindset prevents building the wrong thing</p></li><li><p>QA mindset prevents obvious breakages</p></li><li><p>Release mindset prevents deployment disasters</p></li><li><p>Debug mindset reduces MTTR when reality still breaks you</p></li></ul><p>This is how senior developers become &#8220;go-to&#8221; people.</p><p>Not because they write clever code.<br>Because they deliver reliable outcomes.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h2>A Practical Roadmap (so you actually improve)</h2><h3>Week 1&#8211;2: Debugging upgrade</h3><ul><li><p>Learn logs + correlation IDs</p></li><li><p>Practice reproducing issues locally</p></li><li><p>Document your debugging steps like an investigator</p></li></ul><h3>Week 3&#8211;4: BA upgrade</h3><ul><li><p>For every task, write 5 clarifying questions before coding</p></li><li><p>Convert requirements into &#8220;Given / When / Then&#8221; scenarios</p></li></ul><h3>Week 5&#8211;6: QA upgrade</h3><ul><li><p>Add unit tests for new work</p></li><li><p>For every bug fix, add a regression test</p></li><li><p>Learn integration test basics for your stack</p></li></ul><h3>Week 7&#8211;8: Release upgrade</h3><ul><li><p>Understand your pipeline end-to-end (Jenkins/Azure DevOps/GitHub Actions)</p></li><li><p>Learn feature flags + config management basics</p></li><li><p>Learn safe DB migration patterns (expand/contract)</p></li></ul><div><hr></div><h2>Final Thought</h2><p>A developer who only codes is easy to replace.</p><p>A developer who can:</p><ul><li><p>debug under pressure,</p></li><li><p>clarify business intent,</p></li><li><p>protect quality,</p></li><li><p>and ship safely,</p></li></ul><p>&#8230;becomes a <strong>force multiplier</strong>.</p><p>That&#8217;s not &#8220;extra work.&#8221;</p><p>That&#8217;s what professional software engineering actually is.</p>]]></content:encoded></item><item><title><![CDATA[Jenkinsfile in a .NET Solution]]></title><description><![CDATA[A senior dev&#8217;s practical guide for freshers (what it is, why it matters, and how we actually use it)]]></description><link>https://dotnetfullstackdev.substack.com/p/jenkinsfile-in-a-net-solution</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/jenkinsfile-in-a-net-solution</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Tue, 03 Mar 2026 15:18:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6A32!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When a fresher joins my team, they think CI/CD is &#8220;some DevOps thing&#8221; that happens after coding.</p><p>No.</p><p>In real projects, <strong>your code isn&#8217;t &#8220;done&#8221; until Jenkins proves it can build, test, and ship it</strong>.<br>And the <strong>Jenkinsfile</strong> is the contract that defines that proof.</p><p>If your Jenkinsfile is weak, your delivery is weak. Simple.</p><p><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></p><div><hr></div><h2>What is a Jenkinsfile (in plain words)</h2><p>A <strong>Jenkinsfile</strong> is a text file (checked into your repo) that tells Jenkins:</p><ul><li><p>how to pull the code</p></li><li><p>how to restore dependencies</p></li><li><p>how to build</p></li><li><p>how to run tests</p></li><li><p>how to publish artifacts</p></li><li><p>how to deploy (optional)</p></li></ul><p>It turns your build pipeline into <strong>version-controlled code</strong>.</p><p>So instead of &#8220;Jenkins UI configuration jungle&#8221;, you get <strong>Pipeline as Code</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6A32!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6A32!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6A32!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6A32!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6A32!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6A32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3649221,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/189773560?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6A32!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!6A32!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!6A32!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!6A32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd1be309-2db2-4d89-9493-f46e021269d7_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Where it sits in a .NET repo</h2><p>Typical structure:</p><pre><code>MySolution/
  src/
    MyApi/
    MyLib/
  tests/
    MyApi.Tests/
  Jenkinsfile
  MySolution.sln</code></pre><p>Put <code>Jenkinsfile</code> at repo root unless you have a strong reason not to.</p><div><hr></div><h2>What a good Jenkins pipeline does for .NET</h2><p>A clean pipeline usually has these stages:</p><ol><li><p>Checkout</p></li><li><p>Restore</p></li><li><p>Build</p></li><li><p>Test (with coverage)</p></li><li><p>Publish (build output)</p></li><li><p>Archive artifacts</p></li><li><p>Optional: Quality gates (Sonar), security scans, docker build, deploy</p></li></ol><p>This is not &#8220;extra&#8221;.<br><br>This is basic engineering hygiene.</p><div><hr></div><h1>A solid Jenkinsfile for .NET (Linux agent)</h1><p>This is a <strong>working baseline</strong> for most teams.</p><pre><code>pipeline {
  agent any

  options {
    timestamps()
    ansiColor(&#8217;xterm&#8217;)
    skipDefaultCheckout(true)
    buildDiscarder(logRotator(numToKeepStr: &#8216;30&#8217;))
  }

  environment {
    DOTNET_CLI_TELEMETRY_OPTOUT = &#8216;1&#8217;
    DOTNET_NOLOGO = &#8216;1&#8217;
    CONFIGURATION = &#8216;Release&#8217;
    SOLUTION = &#8216;MySolution.sln&#8217;
    TEST_RESULTS_DIR = &#8216;TestResults&#8217;
  }

  stages {

    stage(&#8217;Checkout&#8217;) {
      steps {
        checkout scm
        sh &#8216;dotnet --info&#8217;
      }
    }

    stage(&#8217;Restore&#8217;) {
      steps {
        sh &#8220;dotnet restore ${SOLUTION}&#8221;
      }
1    }

    stage(&#8217;Build&#8217;) {
      steps {
        sh &#8220;dotnet build ${SOLUTION} -c ${CONFIGURATION} --no-restore&#8221;
      }
    }

    stage(&#8217;Test&#8217;) {
      steps {
        sh &#8220;&#8221;&#8220;
          mkdir -p ${TEST_RESULTS_DIR}
          dotnet test ${SOLUTION} -c ${CONFIGURATION} --no-build \
            --logger &#8220;trx;LogFileName=test-results.trx&#8221; \
            --results-directory ${TEST_RESULTS_DIR}
        &#8220;&#8221;&#8220;
      }
      post {
        always {
          junit allowEmptyResults: true, testResults: &#8220;${TEST_RESULTS_DIR}/**/*.trx&#8221;
          archiveArtifacts allowEmptyArchive: true, artifacts: &#8220;${TEST_RESULTS_DIR}/**/*&#8221;
        }
      }
    }

    stage(&#8217;Publish&#8217;) {
      steps {
        sh &#8220;&#8221;&#8220;
          rm -rf out || true
          dotnet publish src/MyApi/MyApi.csproj -c ${CONFIGURATION} --no-build -o out/MyApi
        &#8220;&#8221;&#8220;
      }
    }

    stage(&#8217;Archive Build Output&#8217;) {
      steps {
        archiveArtifacts artifacts: &#8220;out/**&#8221;, fingerprint: true
      }
    }
  }

  post {
    success {
      echo &#8220;&#9989; Build + Test + Publish succeeded.&#8221;
    }
    failure {
      echo &#8220;&#10060; Pipeline failed. Fix it before you touch anything else.&#8221;
    }
    always {
      cleanWs(deleteDirs: true, disableDeferredWipeout: true)
    }
  }
}</code></pre><h3>What this does (in fresher language)</h3><ul><li><p><strong>restore</strong>: downloads NuGet packages</p></li><li><p><strong>build</strong>: compiles solution</p></li><li><p><strong>test</strong>: runs tests + stores results</p></li><li><p><strong>publish</strong>: creates deployable output (dlls + config)</p></li><li><p><strong>archive</strong>: stores the output in Jenkins as a downloadable artifact</p></li></ul><div><hr></div><h1>Windows agent version (if your org uses Windows nodes)</h1><p>Just replace <code>sh</code> with <code>bat</code> and path formatting.</p><pre><code>pipeline {
  agent any

  options {
    timestamps()
    skipDefaultCheckout(true)
    buildDiscarder(logRotator(numToKeepStr: &#8216;30&#8217;))
  }

  environment {
    DOTNET_CLI_TELEMETRY_OPTOUT = &#8216;1&#8217;
    DOTNET_NOLOGO = &#8216;1&#8217;
    CONFIGURATION = &#8216;Release&#8217;
    SOLUTION = &#8216;MySolution.sln&#8217;
    TEST_RESULTS_DIR = &#8216;TestResults&#8217;
  }

  stages {

    stage(&#8217;Checkout&#8217;) {
      steps {
        checkout scm
        bat &#8216;dotnet --info&#8217;
      }
    }

    stage(&#8217;Restore&#8217;) {
      steps {
        bat &#8220;dotnet restore %SOLUTION%&#8221;
      }
    }

    stage(&#8217;Build&#8217;) {
      steps {
        bat &#8220;dotnet build %SOLUTION% -c %CONFIGURATION% --no-restore&#8221;
      }
    }

    stage(&#8217;Test&#8217;) {
      steps {
        bat &#8220;&#8221;&#8220;
          if not exist %TEST_RESULTS_DIR% mkdir %TEST_RESULTS_DIR%
          dotnet test %SOLUTION% -c %CONFIGURATION% --no-build ^
            --logger &#8220;trx;LogFileName=test-results.trx&#8221; ^
            --results-directory %TEST_RESULTS_DIR%
        &#8220;&#8221;&#8220;
      }
      post {
        always {
          junit allowEmptyResults: true, testResults: &#8220;%TEST_RESULTS_DIR%/**/*.trx&#8221;
          archiveArtifacts allowEmptyArchive: true, artifacts: &#8220;%TEST_RESULTS_DIR%/**/*&#8221;
        }
      }
    }

    stage(&#8217;Publish&#8217;) {
      steps {
        bat &#8220;&#8221;&#8220;
          if exist out rmdir /s /q out
          dotnet publish src\\MyApi\\MyApi.csproj -c %CONFIGURATION% --no-build -o out\\MyApi
        &#8220;&#8221;&#8220;
      }
    }

    stage(&#8217;Archive Build Output&#8217;) {
      steps {
        archiveArtifacts artifacts: &#8220;out/**&#8221;, fingerprint: true
      }
    }
  }

  post {
    always {
      cleanWs()
    }
  }
}</code></pre><div><hr></div><h1>The &#8220;real-world&#8221; upgrades senior teams add</h1><h2>1) Branch-based behavior (main vs feature)</h2><p>You don&#8217;t deploy feature branches.</p><pre><code>when { branch &#8216;main&#8217; }</code></pre><p>Example:</p><pre><code>stage(&#8217;Deploy&#8217;) {
  when { branch &#8216;main&#8217; }
  steps {
    echo &#8220;Deploying only from main...&#8221;
  }
}</code></pre><div><hr></div><h2>2) Multi-branch Pipeline (how teams run Jenkins in 2026)</h2><p>In Jenkins, you create a <strong>Multibranch Pipeline</strong> job.</p><p>Then Jenkins automatically:</p><ul><li><p>finds Jenkinsfile in each branch</p></li><li><p>runs PR builds</p></li><li><p>shows status on PR</p></li></ul><p>This is how professional teams work. Not &#8220;click-build&#8221;.</p><div><hr></div><h2>3) Code coverage (real expectation)</h2><p>Add coverage collector (example using coverlet built-in support):</p><pre><code>dotnet test MySolution.sln -c Release --no-build \
  /p:CollectCoverage=true \
  /p:CoverletOutputFormat=cobertura \
  /p:CoverletOutput=TestResults/coverage/</code></pre><p>Then publish cobertura report via a plugin if your Jenkins has it.</p><div><hr></div><h2>4) SonarQube Quality Gate (common in enterprises)</h2><p>Typical flow:</p><ul><li><p>Begin analysis</p></li><li><p>Build + Test</p></li><li><p>End analysis</p></li><li><p>Wait for quality gate</p></li></ul><p>(Your exact code depends on how Sonar is configured in your org, but the concept is always this.)</p><div><hr></div><h2>5) Docker build (modern microservices)</h2><p>If you ship containers:</p><pre><code>stage(&#8217;Docker Build&#8217;) {
  steps {
    sh &#8216;docker build -t myapi:${BUILD_NUMBER} -f src/MyApi/Dockerfile .&#8217;
  }
}</code></pre><div><hr></div><h1>Common fresher mistakes (and why seniors get angry)</h1><h3>&#10060; Mistake 1: Restore every time without caching</h3><p>Your builds get slow, people stop trusting CI, and then quality dies.</p><p><strong>Fix:</strong> Use Jenkins workspace caching or a persistent agent home so NuGet cache survives across builds.</p><h3>&#10060; Mistake 2: &#8220;Build passed so we can skip tests&#8221;</h3><p>Trash mindset.<br><br>Tests are part of the build.</p><h3>&#10060; Mistake 3: Publishing from random branches</h3><p>That&#8217;s how you deploy garbage to production.</p><h3>&#10060; Mistake 4: Secrets inside Jenkins file</h3><p>Never hardcode:</p><ul><li><p>connection strings</p></li><li><p>API keys</p></li><li><p>tokens</p></li></ul><p>Use Jenkins Credentials + environment injection.</p><div><hr></div><h1>What I tell every fresher on Day 1</h1><p>If you want to become a strong .NET engineer:</p><ul><li><p>Learn your code &#9989;</p></li><li><p>Learn your build pipeline &#9989;</p></li><li><p>Learn how the artifact is produced &#9989;</p></li><li><p>Learn how it gets deployed &#9989;</p></li></ul><p>Because at senior level, you&#8217;re not paid for &#8220;writing code&#8221;.<br><br>You&#8217;re paid for delivering <strong>reliable software</strong>.</p>]]></content:encoded></item><item><title><![CDATA[Are Design Patterns, Clean Architecture, and System Design Dead?]]></title><description><![CDATA[If AI Already Knows Everything, Why Should Developers Learn It?]]></description><link>https://dotnetfullstackdev.substack.com/p/are-design-patterns-clean-architecture</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/are-design-patterns-clean-architecture</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sat, 28 Feb 2026 16:39:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Ucnq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></p><p>There&#8217;s a quiet fear moving through development teams.</p><p>It&#8217;s not loud.<br>It&#8217;s not dramatic.<br>But it&#8217;s there.</p><p>You can feel it in conversations.</p><p>&#8220;AI can generate clean architecture.&#8221;<br>&#8220;AI knows all design patterns.&#8221;<br>&#8220;AI can design scalable systems.&#8221;</p><p>And then comes the real question:</p><blockquote><p>If AI is preloaded with patterns and architectures&#8230;<br>Why should I memorize Singleton, Factory, CQRS, DDD, Clean Architecture, or CAP theorem?</p></blockquote><p>Let&#8217;s answer this honestly.</p><p>Not defensively.</p><p>Not emotionally.</p><p>But structurally.</p><div><hr></div><h1>AI Knows Patterns. It Does Not Understand Trade-offs.</h1><p>This is the first distinction developers must internalize.</p><p>AI has seen:</p><ul><li><p>Millions of examples of Repository pattern</p></li><li><p>Thousands of Clean Architecture implementations</p></li><li><p>Countless microservice templates</p></li><li><p>Infinite Singleton mistakes</p></li></ul><p>It can reproduce patterns.</p><p>But reproduction is not judgment.</p><p>When you design a system, you are not selecting patterns because they exist.</p><p>You are selecting patterns because:</p><ul><li><p>You understand constraints.</p></li><li><p>You understand failure modes.</p></li><li><p>You understand scale.</p></li><li><p>You understand domain complexity.</p></li><li><p>You understand operational cost.</p></li></ul><p>AI suggests patterns based on statistical frequency.</p><p>Architects choose patterns based on contextual necessity.</p><p>That difference is everything.</p><div><hr></div><h1>Memorizing Patterns Is Not About Recall. It&#8217;s About Mental Models.</h1><p>Let&#8217;s be honest.</p><p>No senior engineer memorizes patterns like flashcards.</p><p>They internalize mental models.</p><p>When a senior hears:</p><blockquote><p>&#8220;High read, low write, multi-region system with eventual consistency.&#8221;</p></blockquote><p>Their brain automatically activates:</p><ul><li><p>Caching strategy</p></li><li><p>Read replicas</p></li><li><p>CQRS possibility</p></li><li><p>Event-driven architecture</p></li><li><p>CAP trade-offs</p></li><li><p>Idempotency design</p></li></ul><p>That reflex does not come from memorization.</p><p>It comes from structured understanding.</p><p>AI cannot replace your mental model.</p><p>It can only generate based on its training.</p><p>Without internal mental models, you cannot evaluate AI&#8217;s output.</p><div><hr></div><h1>AI Is a Pattern Reproducer. Developers Are Context Interpreters.</h1><p>Let&#8217;s compare.</p><p>AI:</p><ul><li><p>Detects pattern similarity</p></li><li><p>Generates common architecture</p></li><li><p>Suggests typical solutions</p></li></ul><p>Developer:</p><ul><li><p>Evaluates non-functional requirements</p></li><li><p>Considers team skill level</p></li><li><p>Accounts for deployment constraints</p></li><li><p>Predicts operational burden</p></li><li><p>Assesses long-term maintainability</p></li><li><p>Evaluates organizational culture</p></li></ul><p>Architecture is not just technical.</p><p>It is organizational.</p><p>AI cannot read your org chart.</p><div><hr></div><h1>Clean Architecture Is Not Code Structure. It&#8217;s Responsibility Separation.</h1><p>AI can generate a Clean Architecture folder structure.</p><p>It cannot ensure:</p><ul><li><p>Proper dependency direction</p></li><li><p>Business logic isolation</p></li><li><p>Domain purity</p></li><li><p>Real boundary enforcement</p></li><li><p>Team discipline over time</p></li></ul><p>Clean Architecture is less about code and more about thinking.</p><p>Without understanding dependency inversion, AI-generated layers become cosmetic.</p><p>And cosmetic architecture collapses under complexity.</p><div><hr></div><h1>System Design Is Decision-Making Under Uncertainty</h1><p>System design exists because of trade-offs.</p><p>Latency vs consistency.<br>Cost vs scalability.<br>Simplicity vs flexibility.<br>Performance vs readability.</p><p>AI can suggest a &#8220;scalable architecture.&#8221;</p><p>But scalable for:</p><ul><li><p>1,000 users?</p></li><li><p>1 million users?</p></li><li><p>Global?</p></li><li><p>Real-time?</p></li><li><p>Batch?</p></li><li><p>Regulated industry?</p></li></ul><p>AI does not own consequences.</p><p>Architects do.</p><p>That is why system design cannot disappear.</p><div><hr></div><h1>The Illusion of &#8220;Preloaded Knowledge&#8221;</h1><p>Yes, AI has been trained on:</p><ul><li><p>Martin Fowler</p></li><li><p>Uncle Bob</p></li><li><p>Microsoft docs</p></li><li><p>StackOverflow</p></li><li><p>GitHub repositories</p></li></ul><p>But knowledge is not ownership.</p><p>AI doesn&#8217;t:</p><ul><li><p>Refactor your production monolith safely</p></li><li><p>Debug your memory leak in staging</p></li><li><p>Sit through incident postmortems</p></li><li><p>Feel SLA pressure</p></li><li><p>Answer to executives when systems fail</p></li></ul><p>Architecture knowledge matters because consequences are real.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ucnq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ucnq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ucnq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png" width="1024" height="1536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3730033,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/189476549?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ucnq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!Ucnq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6fb645f-1275-4493-bcdd-819a6fb26cf0_1024x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h1>Why Developers Still Need to Learn Design Patterns</h1><p>Because patterns are not templates.</p><p>They are:</p><ul><li><p>Reusable solutions to recurring problems</p></li><li><p>Vocabulary for technical communication</p></li><li><p>Shared understanding across teams</p></li><li><p>Conceptual compression tools</p></li></ul><p>When you say:</p><blockquote><p>&#8220;Let&#8217;s apply Strategy pattern here.&#8221;</p></blockquote><p>You compress an entire explanation into two words.</p><p>If you rely only on AI and never internalize patterns:</p><p>You cannot communicate at that abstraction level.</p><p>And communication is seniority.</p><div><hr></div><h1>AI Amplifies Good Architects. It Exposes Weak Ones.</h1><p>Here is the uncomfortable truth.</p><p>AI will not replace strong architects.</p><p>It will expose shallow ones.</p><p>If you don&#8217;t understand:</p><ul><li><p>Thread safety</p></li><li><p>Transaction boundaries</p></li><li><p>Data consistency</p></li><li><p>Domain modeling</p></li><li><p>SOLID principles</p></li></ul><p>You cannot detect when AI gives you a flawed design.</p><p>You will accept incorrect patterns confidently.</p><p>And that is more dangerous than ignorance.</p><div><hr></div><h1>The Real Shift: From Pattern Recall to Pattern Evaluation</h1><p>In the AI era, developers do not need to memorize every pattern detail.</p><p>But they must:</p><ul><li><p>Recognize patterns</p></li><li><p>Understand trade-offs</p></li><li><p>Evaluate applicability</p></li><li><p>Modify for context</p></li><li><p>Reject inappropriate suggestions</p></li></ul><p>AI proposes.</p><p>Architects dispose.</p><div><hr></div><h1>The Role of AI in Modern Architecture</h1><p>AI becomes:</p><ul><li><p>Idea generator</p></li><li><p>Boilerplate accelerator</p></li><li><p>Edge-case enumerator</p></li><li><p>Documentation summarizer</p></li><li><p>Alternative approach suggester</p></li></ul><p>But AI is not:</p><ul><li><p>Risk owner</p></li><li><p>Accountability bearer</p></li><li><p>Context interpreter</p></li><li><p>Long-term maintainer</p></li></ul><p>Architecture survives because responsibility survives.</p><div><hr></div><h1>What Happens If You Stop Learning Patterns?</h1><p>You become dependent.</p><p>Dependent engineers:</p><ul><li><p>Cannot evaluate suggestions</p></li><li><p>Cannot design from first principles</p></li><li><p>Cannot debug architectural failures</p></li><li><p>Cannot scale systems independently</p></li><li><p>Cannot lead technical decisions</p></li></ul><p>You become an operator, not an engineer.</p><p>And operators are automatable.</p><div><hr></div><h1>What Actually Changes in the AI Era</h1><p>Earlier:<br>You memorized patterns to recall them manually.</p><p>Now:<br>You learn patterns to validate AI output.</p><p>The reason changes.<br>The necessity does not.</p><div><hr></div><h1>Architecture Is Thinking, Not Syntax</h1><p>Clean architecture is not about folder structure.</p><p>It is about:</p><ul><li><p>Separation of concerns</p></li><li><p>Dependency flow</p></li><li><p>Domain independence</p></li><li><p>Boundary protection</p></li><li><p>Future-proofing</p></li></ul><p>AI can scaffold layers.</p><p>Only engineers protect boundaries.</p><div><hr></div><h1>Final Perspective</h1><p>Design patterns are not dying.</p><p>They are becoming more important.</p><p>Because in a world where AI generates code instantly:</p><p>The ability to judge architecture becomes the differentiator.</p><p>The question is not:</p><blockquote><p>Does AI know design patterns?</p></blockquote><p>The question is:</p><blockquote><p>Do you know enough to recognize when AI misapplies them?</p></blockquote><p>And that, my friend, is what keeps developers relevant.</p>]]></content:encoded></item><item><title><![CDATA[How I Transformed Myself
A Senior .NET Engineer Explaining AI — To His Noob Intern]]></title><description><![CDATA[https://www.youtube.com/@DotNetFullstackDev]]></description><link>https://dotnetfullstackdev.substack.com/p/how-i-transformed-myself-a-senior</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/how-i-transformed-myself-a-senior</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Fri, 27 Feb 2026 00:26:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kiP9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></p><p>Sit down.</p><p>Close StackOverflow for five minutes.</p><p>You asked me yesterday:</p><blockquote><p>&#8220;Sir&#8230; how did you move from normal backend development into AI systems?<br>Did you learn machine learning?&#8221;</p></blockquote><p>I smiled because every junior asks the wrong question first.</p><p>No.</p><p>I didn&#8217;t learn AI.</p><p>I learned <strong>where AI fits inside software</strong>.</p><p>Let me tell you how it actually happened</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kiP9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kiP9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kiP9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png" width="1024" height="1536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3447587,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/189312364?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kiP9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!kiP9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6d0fcdf-02a1-4ec1-8dca-8f8f5edc633f_1024x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>.</p><div><hr></div><h2>Chapter 1 &#8212; The Day I Realized Coding Was Not the Job</h2><p>When I started my career, I believed software engineering meant writing code.</p><p>Controllers.<br>Repositories.<br>Stored procedures.<br>Fixing null reference exceptions at 2 AM.</p><p>I optimized queries.<br>Reduced API latency.<br>Fought deadlocks.</p><p>And I thought:</p><blockquote><p>&#8220;This is engineering.&#8221;</p></blockquote><p>Then something strange happened.</p><p>Every system I built started looking identical.</p><p>Different business.<br>Same architecture.</p><pre><code>Controller
 &#8594; Service
   &#8594; Repository
     &#8594; Database</code></pre><p>Only nouns changed.</p><p>Customer.<br><br>Invoice.<br><br>Order.<br><br>Ticket.</p><p>Same patterns.</p><p>That&#8217;s when I understood something dangerous:</p><blockquote><p>Coding is repetition wrapped in logic.</p></blockquote><p>And repetition is always the first thing automation replaces.</p><div><hr></div><h2>Chapter 2 &#8212; The Arrival of the &#8220;Alien Words&#8221;</h2><p>Then came the meetings.</p><p>Someone said:</p><ul><li><p>LLM</p></li><li><p>GPT</p></li><li><p>Agents</p></li><li><p>Embeddings</p></li><li><p>AI workflows</p></li></ul><p>Half the room nodded confidently.</p><p>The other half Googled secretly.</p><p>I was in the second half.</p><p>It sounded like the industry suddenly switched from C# to astrophysics.</p><p>My first instinct?</p><p>Ignore it.</p><p>Exactly like senior developers ignored:</p><ul><li><p>async/await</p></li><li><p>cloud computing</p></li><li><p>containers</p></li></ul><p>History repeats.</p><div><hr></div><h2>Chapter 3 &#8212; My First Mistake</h2><p>I tried learning AI the wrong way.</p><p>I opened YouTube.</p><p>Suddenly I was watching:</p><ul><li><p>neural networks</p></li><li><p>gradient descent</p></li><li><p>backpropagation</p></li><li><p>tensor mathematics</p></li></ul><p>After two days I closed everything.</p><p>Because I realized:</p><blockquote><p>This is not my layer.</p></blockquote><p>A database engineer doesn&#8217;t build SSD firmware.</p><p>A web developer doesn&#8217;t design TCP/IP.</p><p>Why was I trying to become an AI researcher?</p><p>Wrong abstraction level.</p><div><hr></div><h2>Chapter 4 &#8212; The Moment Everything Clicked</h2><p>One evening I asked a different question.</p><p>Not:</p><blockquote><p>&#8220;How does AI work?&#8221;</p></blockquote><p>But:</p><blockquote><p>&#8220;How would I integrate this into an ASP.NET application?&#8221;</p></blockquote><p>That changed everything.</p><p>Because suddenly AI looked familiar.</p><p>It wasn&#8217;t intelligence.</p><p>It was an API.</p><p>Just another dependency.</p><p>Like SQL Server once was.</p><div><hr></div><h2>Chapter 5 &#8212; My First Real Experiment</h2><p>I wrote a tiny endpoint.</p><pre><code>POST /summarize</code></pre><p>Input:<br><br>A long document.</p><p>Backend:<br><br>Call LLM API.</p><p>Return:<br><br>Summary.</p><p>That&#8217;s it.</p><p>No magic.</p><p>But something felt different.</p><p>For the first time&#8230;</p><p>My API wasn&#8217;t executing logic.</p><p>It was <strong>reasoning</strong>.</p><p>And I realized:</p><blockquote><p>The future backend doesn&#8217;t just process data.<br><br>It interprets intent.</p></blockquote><div><hr></div><h2>Chapter 6 &#8212; Understanding the True Role of a Developer</h2><p>Listen carefully.</p><p>AI doesn&#8217;t remove engineering.</p><p>It moves responsibility upward.</p><p>Earlier, I decided:</p><ul><li><p>how data flows</p></li><li><p>how logic executes</p></li></ul><p>Now I decide:</p><ul><li><p>what AI is allowed to do</p></li><li><p>what AI must never do</p></li><li><p>how outputs are validated</p></li><li><p>how decisions are audited</p></li></ul><p>The LLM became junior.</p><p>I became architect again.</p><div><hr></div><h2>Chapter 7 &#8212; The Agent Revelation</h2><p>You know what an AI Agent really is?</p><p>Not Jarvis.</p><p>Not consciousness.</p><p>It&#8217;s this:</p><pre><code>while(goalNotAchieved)
{
    DecideNextStep();
    ExecuteTool();
    ObserveResult();
}</code></pre><p>That&#8217;s it.</p><p>A loop.</p><p>We&#8217;ve written this our entire careers.</p><p>Background workers.<br><br>Schedulers.<br><br>Workflow engines.</p><p>Only difference?</p><p>Decision-making moved from if-statements to probability.</p><div><hr></div><h2>Chapter 8 &#8212; The Hard Lesson</h2><p>My first agent failed spectacularly.</p><p>It called tools endlessly.<br><br>Generated nonsense.<br><br>Burned API credits.</p><p>I learned something critical:</p><blockquote><p>AI without guardrails behaves like an infinite while loop.</p></blockquote><p>So I added:</p><ul><li><p>step limits</p></li><li><p>schema validation</p></li><li><p>tool allowlists</p></li><li><p>timeout policies</p></li><li><p>audit logging</p></li></ul><p>Suddenly the system stabilized.</p><p>Not because AI improved.</p><p>Because engineering improved.</p><blockquote><p><em>&#128073;I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a><br></strong>(Instant download, production-ready, no fluff)</em></p></blockquote><div><hr></div><h2>Chapter 9 &#8212; The Biggest Mindset Upgrade</h2><p>Junior developers think AI writes software.</p><p>Experienced developers realize:</p><blockquote><p>AI increases the importance of architecture.</p></blockquote><p>Because now systems must handle uncertainty.</p><p>You don&#8217;t trust outputs blindly.</p><p>You design verification layers.</p><p>Like input validation evolved into output validation.</p><div><hr></div><h2>Chapter 10 &#8212; When I Became AI-Augmented</h2><p>The transition wasn&#8217;t dramatic.</p><p>No certification.<br><br>No career switch.</p><p>One day I noticed:</p><p>Instead of asking,</p><blockquote><p>&#8220;How do I code this feature?&#8221;</p></blockquote><p>I asked,</p><blockquote><p>&#8220;Should this be deterministic logic or probabilistic reasoning?&#8221;</p></blockquote><p>That question changed my design decisions forever.</p><p>Some problems belong to code.</p><p>Some belong to AI.</p><p>Knowing the difference is the new seniority.</p><div><hr></div><h2>Chapter 11 &#8212; What I Told My Team</h2><p>I gathered juniors and said:</p><p>Stop fearing AI.</p><p>You already know 80% required skills:</p><ul><li><p>APIs</p></li><li><p>scalability</p></li><li><p>observability</p></li><li><p>security</p></li><li><p>async workflows</p></li><li><p>distributed systems</p></li></ul><p>AI engineers without backend knowledge struggle.</p><p>Backend engineers who learn AI orchestration thrive.</p><div><hr></div><h2>Chapter 12 &#8212; The Truth Nobody Says</h2><p>AI will not replace developers.</p><p>But developers who only translate requirements into CRUD APIs?</p><p>Yes.</p><p>Because that layer is automatable.</p><p>The future developer designs systems where:</p><ul><li><p>humans define goals</p></li><li><p>AI proposes solutions</p></li><li><p>software enforces correctness</p></li></ul><div><hr></div><h2>Chapter 13 &#8212; My New Architecture Diagram</h2><p>Earlier:</p><pre><code>User &#8594; API &#8594; Business Logic &#8594; DB</code></pre><p>Now:</p><pre><code>User
 &#8594; API
 &#8594; AI Planner
 &#8594; Tool Layer
 &#8594; Validation
 &#8594; Database
 &#8594; Audit</code></pre><p>Same foundation.</p><p>Higher abstraction.</p><div><hr></div><h2>Chapter 14 &#8212; What I Actually Learned</h2><p>Not AI.</p><p>I learned:</p><ul><li><p>uncertainty engineering</p></li><li><p>intelligent orchestration</p></li><li><p>system supervision</p></li><li><p>probabilistic integration</p></li></ul><p>AI didn&#8217;t replace my skills.</p><p>It amplified them.</p><div><hr></div><h2>Chapter 15 &#8212; What I Want You To Understand</h2><p>You don&#8217;t transition by abandoning .NET.</p><p>You transition by asking:</p><blockquote><p>Where should intelligence live in my system?</p></blockquote><p>Once you see that&#8230;</p><p>AI stops being alien.</p><p>It becomes another service.</p><p>Another dependency injection.</p><p>Another architectural decision.<br><br><strong>Keep the Momentum Going &#8212; Support the Journey</strong></p><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h2>Final Words (From Senior to Intern)</h2><p>You asked how I transformed myself.</p><p>I didn&#8217;t chase AI.</p><p>I expanded my definition of software.</p><p>The compiler still runs.<br><br>The API still scales.<br><br>The database still persists.</p><p>But now&#8230;</p><p>My systems can reason.</p><p>And I remain the engineer controlling them.</p>]]></content:encoded></item><item><title><![CDATA[Jitter in Distributed Systems]]></title><description><![CDATA[Why Every Backend Engineer Must Design With It (Not Add It Later)]]></description><link>https://dotnetfullstackdev.substack.com/p/jitter-in-distributed-systems</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/jitter-in-distributed-systems</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sun, 15 Feb 2026 17:23:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rhpC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In distributed systems, failures rarely occur because of a single component breaking. They occur because of <strong>correlated behavior</strong> under stress.</p><p>One of the most common and least understood causes of correlated stress is synchronized timing.</p><p>Jitter exists to break that synchronization.</p><p>This article explains:</p><ul><li><p>What jitter actually is (beyond the buzzword)</p></li><li><p>Why exponential backoff without jitter is dangerous</p></li><li><p>Where jitter should be applied</p></li><li><p>The math behind load smoothing</p></li><li><p>How to implement it correctly in .NET</p></li><li><p>When not to use it</p></li></ul><p>This is a focused technical deep dive.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rhpC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rhpC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 424w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 848w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 1272w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rhpC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png" width="1024" height="458" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:458,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:77774,&quot;alt&quot;:&quot;Retries, Backoff and Jitter - by Team CodeReliant&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Retries, Backoff and Jitter - by Team CodeReliant" title="Retries, Backoff and Jitter - by Team CodeReliant" srcset="https://substackcdn.com/image/fetch/$s_!rhpC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 424w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 848w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 1272w, https://substackcdn.com/image/fetch/$s_!rhpC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde55c4c9-3db4-4a41-9834-9c30f41a9116_1024x458.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h1>1. What Is Jitter?</h1><p>In distributed systems, jitter is:</p><blockquote><p>Controlled randomness applied to timing decisions to prevent synchronized behavior.</p></blockquote><p>It is typically added to:</p><ul><li><p>Retry delays</p></li><li><p>Backoff strategies</p></li><li><p>Cache expiration times</p></li><li><p>Token refresh intervals</p></li><li><p>Distributed lock retries</p></li><li><p>Heartbeat intervals</p></li></ul><p>Jitter is not about randomness for its own sake. It is about <strong>reducing correlated load spikes</strong>.</p><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><h1>2. The Real Problem: Correlated Retries</h1><p>Consider a service that depends on an external API.</p><p>When the dependency fails temporarily, your application retries using exponential backoff:</p><pre><code><code>Attempt 1 &#8594; 2 seconds  
Attempt 2 &#8594; 4 seconds  
Attempt 3 &#8594; 8 seconds  
Attempt 4 &#8594; 16 seconds
</code></code></pre><p>This looks correct.</p><p>However, if 5,000 clients experience failure at roughly the same time, they will retry in synchronized waves:</p><ul><li><p>All retry at 2 seconds</p></li><li><p>All retry at 4 seconds</p></li><li><p>All retry at 8 seconds</p></li></ul><p>This creates <strong>retry storms</strong>.</p><p>Retry storms amplify load during recovery windows.</p><p>Instead of allowing the dependency to recover, the system repeatedly overloads it.</p><p>This is not a retry problem.</p><p>It is a synchronization problem.</p><div><hr></div><h1>3. Why Exponential Backoff Alone Is Insufficient</h1><p>Exponential backoff increases delay between attempts.<br>But it does not desynchronize clients.</p><p>If all clients calculate:</p><pre><code><code>TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
</code></code></pre><p>They will retry at identical timestamps.</p><p>The system load graph will look like:</p><pre><code><code>|      |
|      |
|  /\  |
| /  \ |
|/    \|
</code></code></pre><p>Spikes. Silence. Bigger spikes.</p><p>Spikes cause cascading failures.</p><div><hr></div><h1>4. Jitter Breaks Correlation</h1><blockquote><p>&#128073; I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a></strong><br>(Instant download, production-ready, no fluff)</p></blockquote><p>Instead of retrying at a fixed delay, introduce randomness within the backoff window.</p><p>There are several jitter strategies.</p><div><hr></div><h2>4.1 Simple Additive Jitter</h2><p>Add small randomness to fixed delay.</p><pre><code><code>var baseDelay = TimeSpan.FromSeconds(4);
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000));

await Task.Delay(baseDelay + jitter);
</code></code></pre><p>This reduces perfect alignment but still clusters around the base delay.</p><div><hr></div><h2>4.2 Full Jitter (Recommended)</h2><p>Instead of:</p><pre><code><code>delay = exponential
</code></code></pre><p>Use:</p><pre><code><code>delay = random(0, exponential)
</code></code></pre><p>Implementation:</p><pre><code><code>var retryAttempt = 3;
var exponentialMs = Math.Pow(2, retryAttempt) * 1000;

var delay = Random.Shared.Next(0, (int)exponentialMs);

await Task.Delay(delay);
</code></code></pre><p>This spreads retries across the entire backoff window.</p><p>It significantly reduces peak concurrency.</p><p>This pattern is widely used in large-scale distributed systems.</p><div><hr></div><h1>5. Using Jitter With Polly in .NET</h1><p>Polly is commonly used for retry policies.</p><p>Install:</p><pre><code><code>dotnet add package Polly
</code></code></pre><div><hr></div><h2>5.1 Retry Without Jitter (Not Recommended at Scale)</h2><pre><code><code>var retryPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .WaitAndRetryAsync(5, retryAttempt =&gt;
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
</code></code></pre><p>This will cause synchronized retry waves.</p><div><hr></div><h2>5.2 Retry With Full Jitter (Recommended)</h2><pre><code><code>var retryPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .WaitAndRetryAsync(5, retryAttempt =&gt;
    {
        var maxDelay = Math.Pow(2, retryAttempt);
        var jitterFactor = Random.Shared.NextDouble();

        return TimeSpan.FromSeconds(maxDelay * jitterFactor);
    });
</code></code></pre><p>This ensures retries are distributed across the window.</p><div><hr></div><h1>6. Jitter in Caching Strategies</h1><p>Synchronized cache expiry can cause database overload.</p><p>Example:</p><ul><li><p>Cache TTL = 30 minutes</p></li><li><p>All instances populate cache at startup</p></li><li><p>All expire at exactly 30 minutes</p></li></ul><p>Result:</p><ul><li><p>Sudden database surge</p></li><li><p>CPU spike</p></li><li><p>Latency increase</p></li></ul><p>Correct approach:</p><pre><code><code>var baseTtl = TimeSpan.FromMinutes(30);
var jitterMinutes = Random.Shared.Next(0, 5);

var finalTtl = baseTtl + TimeSpan.FromMinutes(jitterMinutes);
</code></code></pre><p>Now expiration spreads over a time range.</p><p>Load becomes smoother.</p><div><hr></div><h1>7. Jitter in Distributed Locks</h1><p>Consider multiple nodes retrying to acquire a distributed lock (e.g., Redis lock).</p><p>Without jitter:</p><ul><li><p>All retry at fixed interval.</p></li><li><p>Collisions repeat.</p></li><li><p>Lock starvation increases.</p></li></ul><p>With jitter:</p><pre><code><code>var baseDelay = TimeSpan.FromMilliseconds(200);
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 150));

await Task.Delay(baseDelay + jitter);
</code></code></pre><p>Lock acquisition attempts desynchronize.</p><p>Throughput improves.</p><div><hr></div><h1>8. Mathematical Perspective</h1><p>Assume 10,000 clients retry after 4 seconds.</p><p>Without jitter:</p><ul><li><p>Peak load at 4-second mark &#8776; 10,000 requests simultaneously.</p></li></ul><p>With jitter across 4-second window:</p><ul><li><p>Average &#8776; 2,500 per second.</p></li><li><p>Peak significantly reduced.</p></li></ul><p>Same total work.<br>Lower instantaneous stress.</p><p>Distributed systems fail due to peak pressure, not average load.</p><div><hr></div><h1>9. Where Jitter Should Be Considered Mandatory</h1><p>In high-scale backend systems, jitter should be evaluated for:</p><ul><li><p>HTTP retry policies</p></li><li><p>Message queue retries</p></li><li><p>Circuit breaker half-open attempts</p></li><li><p>Cache expiration</p></li><li><p>Token refresh</p></li><li><p>Scheduled jobs</p></li><li><p>Distributed coordination loops</p></li></ul><p>If timing exists and multiple nodes share similar schedules, jitter likely improves stability.</p><div><hr></div><h1>10. When Not to Use Jitter</h1><p>Avoid jitter in:</p><ul><li><p>Hard real-time systems</p></li><li><p>Deterministic control systems</p></li><li><p>Time-sensitive trading algorithms</p></li><li><p>Strict ordering protocols</p></li></ul><p>In these cases, randomness may violate correctness guarantees.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1>11. Engineering Maturity Model</h1><p>Level 1:<br>Retry with fixed delay.</p><p>Level 2:<br>Retry with exponential backoff.</p><p>Level 3:<br>Retry with exponential backoff + jitter.</p><p>Level 4:<br>Analyze synchronization patterns across the entire system.</p><p>Jitter is not a feature.</p><p>It is part of distributed system hygiene.</p><div><hr></div><h1>Final Takeaway</h1><p>If your system:</p><ul><li><p>Retries failures</p></li><li><p>Caches aggressively</p></li><li><p>Scales horizontally</p></li><li><p>Coordinates distributed nodes</p></li></ul><p>Then synchronized timing is a hidden risk.</p><p>Jitter reduces correlated load.<br>It prevents retry storms.<br>It smooths peak pressure.<br>It stabilizes recovery behavior.</p><p>In distributed architecture, small randomness can produce significant reliability gains.</p>]]></content:encoded></item><item><title><![CDATA[The Day I Stopped Confusing Windows Service, Kestrel, and HTTP.sys]]></title><description><![CDATA[A real developer story &#8212; with real production decisions, real failures, and real fixes]]></description><link>https://dotnetfullstackdev.substack.com/p/the-day-i-stopped-confusing-windows</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/the-day-i-stopped-confusing-windows</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sat, 14 Feb 2026 13:58:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7pYQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7pYQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7pYQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 424w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 848w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 1272w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7pYQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png" width="980" height="783" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:783,&quot;width&quot;:980,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;What is the difference between IIS and Kestrel servers in ASP.NET?&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="What is the difference between IIS and Kestrel servers in ASP.NET?" title="What is the difference between IIS and Kestrel servers in ASP.NET?" srcset="https://substackcdn.com/image/fetch/$s_!7pYQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 424w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 848w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 1272w, https://substackcdn.com/image/fetch/$s_!7pYQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56243eb4-91ea-4c1e-989f-92d750580d28_980x783.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I used to think these three were basically the same topic:</p><ul><li><p>Windows Service</p></li><li><p>Kestrel</p></li><li><p>HTTP.sys</p></li></ul><p>I thought:</p><blockquote><p>&#8220;They&#8217;re all just ways to host my .NET API, right?&#8221;</p></blockquote><p>That misunderstanding cost me a full day in debugging.</p><p>Because they are not the same layer.</p><p>They solve different problems.</p><p>And until you understand the <em>layering</em>, you&#8217;ll keep building apps that run on your laptop&#8230; but fail on the server.</p><p>This is my real story of how I finally understood it &#8212; and how you can implement each one properly.</p><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><h2>The real requirement that started it all</h2><p>My project had a very normal enterprise requirement:</p><ol><li><p>Every night, <strong>files are dropped</strong> into a folder (CSV/Excel)</p></li><li><p>A service should <strong>pick them up</strong>, validate them, and push them into SQL</p></li><li><p>Operations team wants a simple internal API endpoint:</p><ul><li><p>&#8220;What files were processed today?&#8221;</p></li><li><p>&#8220;What failed?&#8221;</p></li><li><p>&#8220;How many rows were inserted?&#8221;</p></li></ul></li><li><p>The server is Windows.</p></li><li><p>Nobody wants IIS complexity unless needed.</p></li><li><p>Security team later asks: &#8220;We want Windows authentication.&#8221;</p></li></ol><p>So I ended up using all three:</p><ul><li><p>Windows Service (for the worker)</p></li><li><p>Kestrel (for a simple API)</p></li><li><p>HTTP.sys (when Windows-auth + enterprise constraints came in)</p></li></ul><div><hr></div><h1>Part 1 &#8212; Windows Service (Worker)</h1><h2>Why I needed it (and why console apps are a trap)</h2><p>My first build was a console app.</p><p>It ran perfectly.</p><p>But the moment I:</p><ul><li><p>closed the window</p></li><li><p>logged out</p></li><li><p>restarted the server</p></li></ul><p>&#8230;the processing stopped.</p><p>That&#8217;s when I learned the key difference:</p><h3>A console app is &#8220;someone running it&#8221;.</h3><h3>A Windows Service is &#8220;Windows running it&#8221;.</h3><p>A Windows Service is designed for:</p><ul><li><p><strong>automatic start</strong> at boot</p></li><li><p>running <strong>without a logged-in user</strong></p></li><li><p>clean stop/restart control</p></li><li><p>monitoring and recovery rules</p></li></ul><p>Basically: <strong>production reliability</strong>.</p><div><hr></div><h2>Implementation &#8212; Step-by-step with real explanations</h2><h3>Step 1 &#8212; Create a Worker Service project</h3><pre><code><code>dotnet new worker -n FileProcessorService
cd FileProcessorService
</code></code></pre><p><strong>Why this template?</strong><br>Because this template is not &#8220;just code that runs.&#8221; It gives you:</p><ul><li><p>a hosted lifecycle (start/stop)</p></li><li><p>built-in dependency injection</p></li><li><p>structured logging</p></li><li><p>a background processing model designed for servers</p></li></ul><p>This is the correct base for anything that must run 24/7.</p><div><hr></div><h3>Step 2 &#8212; Add Windows Service integration</h3><pre><code><code>dotnet add package Microsoft.Extensions.Hosting.WindowsServices
</code></code></pre><p><strong>What does this package actually do?</strong><br>Without this package, your worker is just a normal .NET process.</p><p>With this package:</p><ul><li><p>your app understands Windows Service Control Manager (SCM)</p></li><li><p>it receives <strong>Start</strong>, <strong>Stop</strong>, <strong>Shutdown</strong> signals properly</p></li><li><p>it behaves like a real service (not like a console app pretending to be one)</p></li></ul><p>This is the difference between:</p><ul><li><p>&#8220;I can run it&#8221;<br>and</p></li><li><p>&#8220;IT can operate it&#8221;</p></li></ul><div><hr></div><h3>Step 3 &#8212; Configure <code>Program.cs</code> (and why each line matters)</h3><pre><code><code>using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

IHost host = Host.CreateDefaultBuilder(args)
    .UseWindowsService(options =&gt;
    {
        options.ServiceName = "FileProcessorService";
    })
    .ConfigureLogging(logging =&gt;
    {
        logging.ClearProviders();
        logging.AddConsole();   // useful during local debugging
        logging.AddEventLog();  // useful when running as service
    })
    .ConfigureServices(services =&gt;
    {
        services.AddHostedService&lt;Worker&gt;();
    })
    .Build();

await host.RunAsync();
</code></code></pre><p>Let me explain what&#8217;s happening here like I wish someone explained to me:</p><ul><li><p><code>CreateDefaultBuilder</code> sets up the standard hosting environment:</p><ul><li><p>configuration (appsettings.json + environment variables)</p></li><li><p>dependency injection container</p></li><li><p>logging pipeline</p></li></ul></li><li><p><code>UseWindowsService</code> tells the host:</p><ul><li><p>&#8220;Don&#8217;t run like a console app&#8221;</p></li><li><p>&#8220;Run as a Windows service with SCM integration&#8221;</p></li></ul></li><li><p>Logging:</p><ul><li><p><code>AddConsole()</code> helps you when you run locally (<code>dotnet run</code>)</p></li><li><p><code>AddEventLog()</code> is critical because once it runs as a service, you won&#8217;t see console output.<br>Without EventLog, you&#8217;ll feel blind in production.</p></li></ul></li></ul><div><hr></div><h3>Step 4 &#8212; Write <code>Worker.cs</code> correctly (this is where most people mess up)</h3><pre><code><code>using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public sealed class Worker : BackgroundService
{
    private readonly ILogger&lt;Worker&gt; _logger;

    public Worker(ILogger&lt;Worker&gt; logger) =&gt; _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Service started at {time}", DateTimeOffset.Now);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation("Checking folder for new files...");
                ProcessFiles();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "File processing failed");
            }

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }

        _logger.LogInformation("Service stopping at {time}", DateTimeOffset.Now);
    }

    private void ProcessFiles()
    {
        // Real implementation:
        // - scan folder
        // - lock file while processing
        // - validate and parse rows
        // - insert into SQL
        // - move file to Processed/Failed folders
    }
}
</code></code></pre><p><strong>Why the </strong><code>CancellationToken</code><strong> matters so much?</strong><br>Because when Windows stops a service, it doesn&#8217;t &#8220;kill it immediately.&#8221;<br>It sends a stop request.</p><p>If your code ignores cancellation:</p><ul><li><p>your service takes too long to stop</p></li><li><p>it may get force-killed</p></li><li><p>it may corrupt processing state</p></li></ul><p>This is how production issues happen.</p><div><hr></div><h3>Step 5 &#8212; Publish for deployment</h3><pre><code><code>dotnet publish -c Release -r win-x64 --self-contained false -o C:\Services\FileProcessorService
</code></code></pre><p><strong>Why publish?</strong><br>Because servers should not build your project.<br>Servers should run compiled artifacts.</p><p>Publishing creates the final executable + dependencies in a clean folder.</p><div><hr></div><h3>Step 6 &#8212; Install as Windows Service</h3><p>PowerShell (Admin):</p><pre><code><code>New-Service `
  -Name "FileProcessorService" `
  -BinaryPathName "C:\Services\FileProcessorService\FileProcessorService.exe" `
  -DisplayName "File Processor Service" `
  -StartupType Automatic
</code></code></pre><p>Now Windows owns it.</p><p>Your app becomes an operating system-managed process.</p><div><hr></div><h3>Step 7 &#8212; Start and validate</h3><pre><code><code>Start-Service FileProcessorService
Get-Service FileProcessorService
</code></code></pre><p>Then check logs:</p><ul><li><p>Windows Event Viewer &#8594; Windows Logs &#8594; Application</p></li></ul><p>This step matters because:</p><ul><li><p>most failures happen at startup due to config/path permissions</p></li><li><p>EventLog is where you&#8217;ll find the truth</p></li></ul><div><hr></div><h1>Part 2 &#8212; Kestrel (Web API)</h1><h2>Why I used it first for the internal status API</h2><p>Once the worker was stable, Ops team asked:</p><blockquote><p>&#8220;Can we have an endpoint to see status?&#8221;</p></blockquote><p>I didn&#8217;t need enterprise auth initially.<br>I just needed:</p><ul><li><p><code>/health</code></p></li><li><p><code>/processed-files/today</code></p></li><li><p><code>/failed-files/today</code></p></li></ul><p>For this, <strong>Kestrel is perfect</strong>.</p><h3>What Kestrel really is</h3><p>Kestrel is the <strong>default web server inside ASP.NET Core</strong>.</p><p>It is:</p><ul><li><p>fast</p></li><li><p>modern</p></li><li><p>easy</p></li><li><p>cross-platform</p></li></ul><p>It listens directly on a port like </p><p>http://localhost:5070</p><p>.</p><div><hr></div><h2>Implementation &#8212; Step-by-step with explanation</h2><h3>Step 1 &#8212; Create Web API</h3><pre><code><code>dotnet new webapi -n FileStatusApi
cd FileStatusApi
</code></code></pre><h3>Step 2 &#8212; Make it run as Windows Service</h3><pre><code><code>dotnet add package Microsoft.Extensions.Hosting.WindowsServices
</code></code></pre><h3>Step 3 &#8212; Configure <code>Program.cs</code></h3><pre><code><code>var builder = WebApplication.CreateBuilder(args);

builder.Host.UseWindowsService(options =&gt;
{
    options.ServiceName = "FileStatusApiService";
});

builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/health", () =&gt; Results.Ok("OK"));
app.MapControllers();

app.Run();
</code></code></pre><p><strong>Key understanding:</strong><br>Windows Service here controls <em>lifetime</em>.<br>Kestrel controls <em>HTTP serving</em>.</p><p>Different jobs. Same process.</p><div><hr></div><h3>Step 4 &#8212; Set explicit port (don&#8217;t gamble)</h3><p><code>appsettings.json</code>:</p><pre><code><code>{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5070"
      }
    }
  }
}
</code></code></pre><p><strong>Why explicit?</strong><br>In production you need predictability:</p><ul><li><p>firewall rules depend on known ports</p></li><li><p>monitoring depends on known endpoints</p></li><li><p>avoiding port collisions requires control</p></li></ul><div><hr></div><h3>Step 5 &#8212; Publish &amp; install like earlier</h3><pre><code><code>dotnet publish -c Release -r win-x64 -o C:\Services\FileStatusApi
</code></code></pre><pre><code><code>New-Service `
  -Name "FileStatusApiService" `
  -BinaryPathName "C:\Services\FileStatusApi\FileStatusApi.exe" `
  -StartupType Automatic
</code></code></pre><p>Test:</p><ul><li><p><code>http://localhost:5070/health</code></p></li></ul><p>At this point, I thought: &#8220;Done.&#8221;</p><p>Then security came in.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1>Part 3 &#8212; HTTP.sys</h1><h2>Why Kestrel wasn&#8217;t preferred in my enterprise environment</h2><p>Security requirement changed everything:</p><blockquote><p>&#8220;Use Windows Authentication. Domain users only.&#8221;</p></blockquote><p>Now, yes &#8212; you can do Windows Auth with Kestrel, but in many enterprises:</p><ul><li><p>infrastructure teams prefer OS-level HTTP handling</p></li><li><p>they want URL reservations</p></li><li><p>they want certificate bindings controlled centrally</p></li><li><p>they want kernel-mode handling</p></li></ul><p>That&#8217;s where <strong>HTTP.sys</strong> comes in.</p><h3>What HTTP.sys really is</h3><p>HTTP.sys is Windows&#8217; own HTTP stack.</p><p>It&#8217;s not &#8220;a library web server.&#8221;<br>It is an OS feature.</p><p>That means:</p><ul><li><p>Windows controls the port</p></li><li><p>Windows controls auth</p></li><li><p>Windows controls TLS binding</p></li></ul><p>Your app hooks into it.</p><div><hr></div><h2>Implementation &#8212; Step-by-step with explanation</h2><h3>Step 1 &#8212; Enable HTTP.sys hosting in <code>Program.cs</code></h3><pre><code><code>using Microsoft.AspNetCore.Server.HttpSys;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys(options =&gt;
{
    options.UrlPrefixes.Add("http://localhost:5090");

    options.Authentication.Schemes =
        AuthenticationSchemes.Negotiate |
        AuthenticationSchemes.NTLM;

    options.Authentication.AllowAnonymous = false;
});

var app = builder.Build();

app.MapGet("/secure", (HttpContext ctx) =&gt;
{
    return $"Hello {ctx.User.Identity?.Name}";
});

app.Run();
</code></code></pre><p><strong>What this does:</strong></p><ul><li><p>Requests authentication at the OS HTTP layer</p></li><li><p>Your API automatically sees the authenticated user</p></li><li><p>No tokens, no login screen, no custom auth middleware required</p></li></ul><div><hr></div><h3>Step 2 &#8212; The critical step: URL reservation (URLACL)</h3><pre><code><code>netsh http add urlacl url=http://localhost:5090/ user=Everyone
</code></code></pre><p><strong>Why is this needed?</strong><br>HTTP.sys protects URL prefixes.<br>If your service runs under a normal account, Windows may block it from binding that URL unless reserved.</p><p>Without this:</p><ul><li><p>service might fail on start</p></li><li><p>you&#8217;ll waste time blaming your code</p></li><li><p>but the problem is OS permission</p></li></ul><div><hr></div><h3>Step 3 &#8212; Publish &amp; install as service</h3><p>Same as before.</p><p>Test:</p><ul><li><p><code>http://localhost:5090/secure</code></p></li><li><p>It should return your domain identity automatically.</p></li></ul><div><hr></div><blockquote><p>&#128073; I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a></strong><br>(Instant download, production-ready, no fluff)</p></blockquote><h1>Final clarity &#8212; the mental model I now use</h1><p>If you remember only this, you&#8217;ll never get confused again:</p><h3>Windows Service = &#8220;How my app runs&#8221;</h3><h3>Kestrel = &#8220;How my app serves HTTP (cross-platform)&#8221;</h3><h3>HTTP.sys = &#8220;How Windows serves HTTP (OS-level)&#8221;</h3><p>Different layers.</p><p>Not competitors.</p>]]></content:encoded></item><item><title><![CDATA[Without Really Knowing Why They Existed, I Used HTTP, HTTPS, SSL, and TLS for Years]]></title><description><![CDATA[For a long time, these words floated around my career like background noise.]]></description><link>https://dotnetfullstackdev.substack.com/p/without-really-knowing-why-they-existed</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/without-really-knowing-why-they-existed</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Fri, 06 Feb 2026 10:46:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!21sL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!21sL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!21sL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!21sL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!21sL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!21sL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!21sL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg" width="1024" height="533" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:533,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;HTTP and HTTPS: Creating and handling HTTP and HTTPS requests | by Webclues  Infotech Private Limited | Medium&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="HTTP and HTTPS: Creating and handling HTTP and HTTPS requests | by Webclues  Infotech Private Limited | Medium" title="HTTP and HTTPS: Creating and handling HTTP and HTTPS requests | by Webclues  Infotech Private Limited | Medium" srcset="https://substackcdn.com/image/fetch/$s_!21sL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!21sL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!21sL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!21sL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60be8bba-bc56-49df-b2f7-da15627135b1_1024x533.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For a long time, these words floated around my career like background noise.</p><p>HTTP<br>HTTPS<br>SSL<br>TLS</p><p>I used them.<br>Configured them.<br>Enabled HTTPS because &#8220;security team said so.&#8221;</p><p>But if someone had asked me early on:</p><blockquote><p>&#8220;Why do we actually need all of this?&#8221;<br>&#8220;What really happens when you type a URL and hit Enter?&#8221;</p></blockquote><p>I would&#8217;ve given a half-confident answer and quietly hoped the conversation moved on.</p><p>Not because I was careless &#8212;<br>but because <strong>everything worked</strong>, and that was enough.</p><p>Until one day, it wasn&#8217;t.</p><div><hr></div><h2>The Moment I Realized I Was Guessing</h2><p>The real realization didn&#8217;t come from a book.</p><p>It came from a production issue.</p><p>A website was loading&#8230; but behaving strangely.<br>APIs were reachable, but cookies weren&#8217;t sticking.<br>Some users saw warnings. Some didn&#8217;t.</p><p>Someone asked:</p><blockquote><p>&#8220;Is this HTTP or HTTPS?&#8221;<br>&#8220;Which TLS version is negotiated?&#8221;<br>&#8220;Is the certificate chain correct?&#8221;</p></blockquote><p>I nodded.</p><p>Then I opened the browser dev tools and realized something uncomfortable:</p><p>I had been <strong>using the web</strong>, not <strong>understanding it</strong>.</p><p>That&#8217;s when I decided to slow down and actually follow what happens when a browser hits a website.</p><div><hr></div><h2>It All Starts With a Lie We Tell Ourselves</h2><p>We often say:</p><blockquote><p>&#8220;The browser calls the server.&#8221;</p></blockquote><div class="pullquote"><p>That&#8217;s not wrong &#8212; but it hides a <em>lot</em>.<br><em><strong><br>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>When you type:</p><pre><code>https://example.com</code></pre><p>You&#8217;re not &#8220;calling a server&#8221;.</p><p>You&#8217;re triggering a <strong>sequence of negotiations, lookups, handshakes, and agreements</strong> &#8212; most of which happen before <em>any</em> application code runs.</p><p>And that&#8217;s why HTTP alone was never enough.</p><div><hr></div><h2>Step 1: The Browser Doesn&#8217;t Know Where the Website Lives</h2><p>The first truth:</p><blockquote><p><strong>The browser has no idea where </strong><code>example.com</code><strong> is.</strong></p></blockquote><p>So the very first thing it does is ask:</p><blockquote><p>&#8220;Who knows the IP address for this name?&#8221;</p></blockquote><p>That&#8217;s a DNS lookup.</p><p>No HTTP yet.<br>No TLS yet.<br>Just <em>name resolution</em>.</p><p>Only after DNS answers does the browser know <strong>which machine</strong> to talk to.</p><div><hr></div><h2>Step 2: The Browser Opens a Raw Connection</h2><p>Now the browser has an IP address.</p><p>So it opens a <strong>TCP connection</strong>.</p><p>This is still not HTTP.<br>This is still not HTTPS.</p><p>It&#8217;s just:</p><blockquote><p>&#8220;Hey, I can reach you.&#8221;</p></blockquote><p>At this point:</p><ul><li><p>data is unencrypted</p></li><li><p>identity is unverified</p></li><li><p>trust is zero</p></li></ul><p>This is where <strong>HTTP originally lived</strong>.</p><div><hr></div><h2>When HTTP Was Enough (And Why It Stopped Being Enough)</h2><p>Early web days were simple.</p><p>HTTP meant:</p><ul><li><p>plain text requests</p></li><li><p>plain text responses</p></li><li><p>anyone on the network could read or modify traffic</p></li></ul><p>It worked because:</p><ul><li><p>websites were informational</p></li><li><p>no logins</p></li><li><p>no payments</p></li><li><p>no personal data</p></li></ul><p>But the moment we started sending:</p><ul><li><p>passwords</p></li><li><p>cookies</p></li><li><p>tokens</p></li><li><p>personal data</p></li></ul><p>HTTP became dangerous.</p><p>Anyone in between could:</p><ul><li><p>read your credentials</p></li><li><p>hijack sessions</p></li><li><p>modify responses</p></li></ul><p>That&#8217;s when the web needed something <em>below</em> HTTP.</p><div><hr></div><h2>Enter SSL and TLS (The Invisible Guardians)</h2><p>Here&#8217;s the part that confused me for years:</p><p><strong>SSL and TLS are not web technologies.</strong><br>They don&#8217;t know about URLs, controllers, or APIs.</p><p>They live <strong>below HTTP</strong>.</p><p>Their job is simple but critical:</p><blockquote><p>&#8220;Make this connection private and trustworthy.&#8221;</p></blockquote><p>Before a single HTTP request is sent, TLS steps in and asks:</p><ul><li><p>Who are you?</p></li><li><p>Can I trust you?</p></li><li><p>Can we talk securely?</p></li><li><p>How should we encrypt our conversation?</p></li></ul><p>Only if all answers are acceptable does HTTP get a chance to speak.</p><div><hr></div><h2>Step 3: The TLS Handshake (The Real Magic)</h2><p>This is the moment most people never see.</p><p>The browser and server perform a <strong>TLS handshake</strong>.</p><p>Conceptually, this happens:</p><ul><li><p>Browser says: &#8220;Here are encryption methods I support&#8221;</p></li><li><p>Server responds: &#8220;Here&#8217;s my certificate&#8221;</p></li><li><p>Browser verifies:</p><ul><li><p>Is this certificate valid?</p></li><li><p>Is it issued by a trusted authority?</p></li><li><p>Does it match the domain?</p></li><li><p>Is it expired or revoked?</p></li></ul></li></ul><p>If anything fails &#8212; <strong>everything stops</strong>.</p><p>No HTTP request.<br>No controller.<br>No API.</p><p>This is why HTTPS failures feel so dramatic.</p><div><hr></div><h2>Why Certificates Exist (And Why They Matter)</h2><p>Certificates solve one critical problem:</p><blockquote><p><strong>How do you know the server you&#8217;re talking to is who it claims to be?</strong></p></blockquote><p>Without certificates:</p><ul><li><p>anyone could pretend to be <code>example.com</code></p></li><li><p>encryption alone wouldn&#8217;t help</p></li><li><p>you&#8217;d encrypt data&#8230; to the wrong person</p></li></ul><p>Certificates tie:</p><ul><li><p>domain name</p></li><li><p>public key</p></li><li><p>trusted authority</p></li></ul><p>This is how browsers build trust <strong>without knowing your server personally</strong>.</p><div><hr></div><h2>Step 4: HTTPS Is Just HTTP With Armor</h2><p>Here&#8217;s the big realization that clicked for me:</p><blockquote><p><strong>HTTPS is not a different protocol.</strong><br>It&#8217;s HTTP <em>inside</em> a secure tunnel.</p></blockquote><p>Once TLS finishes:</p><ul><li><p>HTTP requests are sent normally</p></li><li><p>headers, cookies, payloads behave the same</p></li><li><p>your application code sees no difference</p></li></ul><p>The difference is:</p><ul><li><p>nobody else can read it</p></li><li><p>nobody can alter it</p></li><li><p>the server identity is verified</p></li></ul><p>That&#8217;s it.</p><p>HTTPS doesn&#8217;t change how you write APIs.<br>It changes <strong>who can see and trust them</strong>.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h2>Where Cookies, Sessions, and Auth Depend on This</h2><p>This is where everything connects.</p><p>Cookies only work securely because:</p><ul><li><p>HTTPS protects them from being stolen</p></li><li><p>TLS prevents session hijacking</p></li><li><p>browsers enforce secure cookie rules</p></li></ul><p>Without HTTPS:</p><ul><li><p>authentication falls apart</p></li><li><p>sessions become vulnerable</p></li><li><p>trust collapses</p></li></ul><p>That&#8217;s why modern browsers now <strong>block sensitive features</strong> on HTTP.</p><p>Security moved from &#8220;nice to have&#8221; to &#8220;non-negotiable&#8221;.</p><div><hr></div><h2>Step 5: The Actual HTTP Request Finally Happens</h2><p>Only now does the browser send:</p><pre><code><code>GET / HTTP/1.1
Host: example.com
</code></code></pre><p>At this point:</p><ul><li><p>the connection is encrypted</p></li><li><p>the server is trusted</p></li><li><p>cookies are attached</p></li><li><p>headers are intact</p></li></ul><p>Your server code finally runs.</p><p>Controllers execute.<br>Middleware triggers.<br>Responses are generated.</p><p>But all of this sits <strong>on top of</strong> what already happened.</p><div><hr></div><h2>Why We Need All of This (The Simple Truth)</h2><p>Each layer exists because the one above it <strong>assumes safety</strong>.</p><ul><li><p>HTTP assumes a reliable connection</p></li><li><p>HTTPS assumes a trusted identity</p></li><li><p>Cookies assume encryption</p></li><li><p>Authentication assumes integrity</p></li></ul><p>Remove one layer, and the rest become fragile.</p><p>This isn&#8217;t complexity for the sake of it.</p><p>It&#8217;s <strong>defense built from experience</strong>.</p><div><hr></div><h2>The Moment It Finally Made Sense to Me</h2><p>The day this truly clicked was when I realized:</p><blockquote><p><strong>Application security doesn&#8217;t start in your code.<br>It starts before your code even runs.</strong></p></blockquote><p>HTTP, HTTPS, TLS, certificates &#8212;<br>they&#8217;re not optional add-ons.</p><p>They&#8217;re the foundation that makes modern applications possible.</p><div><hr></div><h2>Final Thought</h2><blockquote><p>&#128073; I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a></strong><br>(Instant download, production-ready, no fluff)</p></blockquote><p>Next time you type a URL and hit Enter, remember:</p><p>You&#8217;re not &#8220;opening a website&#8221;.</p><p>You&#8217;re:</p><ul><li><p>resolving identity</p></li><li><p>negotiating trust</p></li><li><p>agreeing on encryption</p></li><li><p>establishing privacy</p></li><li><p>and only then&#8230; exchanging data</p></li></ul><p>All in a fraction of a second.</p><p>Once you see that,<br>words like HTTP, HTTPS, SSL, and TLS stop being confusing acronyms.</p><p>They become <strong>quiet guardians</strong> &#8212; doing their job so your application code can do its own.</p><p>And when you understand that, you stop taking the web for granted.</p><p>You start respecting it.</p>]]></content:encoded></item><item><title><![CDATA[I Thought “Generating a Report” Was the Hard Part]]></title><description><![CDATA[Until I Saw What Happens After]]></description><link>https://dotnetfullstackdev.substack.com/p/i-thought-generating-a-report-was</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/i-thought-generating-a-report-was</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Tue, 03 Feb 2026 16:09:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DUsM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DUsM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DUsM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 424w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 848w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 1272w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DUsM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png" width="1456" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Streaming Kafka Data to Azure Blob Storage: A Comprehensive Guide&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Streaming Kafka Data to Azure Blob Storage: A Comprehensive Guide" title="Streaming Kafka Data to Azure Blob Storage: A Comprehensive Guide" srcset="https://substackcdn.com/image/fetch/$s_!DUsM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 424w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 848w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 1272w, https://substackcdn.com/image/fetch/$s_!DUsM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14261d56-dd67-49df-b0f4-56af2e4e47b1_1999x1043.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br>For a long time in my career, I believed report generation was the end of the story.</p><p>A user clicks <strong>Download Report</strong>.<br>The backend queries the database.<br>A file is generated.<br>The user gets it.</p><p>Done.</p><p>That mental model worked &#8212; until it didn&#8217;t.</p><p>The moment reports stopped being:</p><ul><li><p>small</p></li><li><p>synchronous</p></li><li><p>user-triggered</p></li><li><p>single-consumer</p></li></ul><p>Everything changed.</p><p>This blog is about that moment.<br>And about a flow that finally made sense to me:</p><p><strong>.NET Application &#8594; Kafka &#8594; Ingestion Service &#8594; Shared Persistence / Blob Storage</strong></p><p>At first, this flow looked unnecessarily complex.</p><p>Later, it felt inevitable.</p><div><hr></div><h2>When Reports Were Simple (and Why That Was Deceptive)</h2><p>Early on, reports lived inside the application.</p><p>A controller would:</p><ul><li><p>fetch data</p></li><li><p>format it</p></li><li><p>write a file</p></li><li><p>stream it back</p></li></ul><p>It felt productive.</p><p>But over time, requirements grew quietly:</p><ul><li><p>reports became large</p></li><li><p>generation took minutes</p></li><li><p>users wanted async processing</p></li><li><p>multiple teams wanted the same report</p></li><li><p>reports needed to be stored, audited, reprocessed</p></li></ul><p>Suddenly, &#8220;generate and return&#8221; wasn&#8217;t enough.</p><div class="pullquote"><p>That&#8217;s when the system started pushing back.<br><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><div><hr></div><h2>The First Cracks: Reports Are Not Requests</h2><p>The first realization was subtle but important:</p><blockquote><p><strong>A report is not a request-response problem.</strong></p></blockquote><p>A report is:</p><ul><li><p>heavy</p></li><li><p>long-running</p></li><li><p>retryable</p></li><li><p>replayable</p></li><li><p>consumed by more than one party</p></li></ul><p>Treating it like an HTTP request forces:</p><ul><li><p>timeouts</p></li><li><p>retries from browsers</p></li><li><p>duplicated work</p></li><li><p>tight coupling between UI and backend</p></li></ul><p>Once I accepted that, the architecture had to change.</p><div><hr></div><h2>Step 1: The .NET Application Stops &#8220;Doing&#8221; the Report</h2><p>The biggest shift was this:</p><p>The .NET application stopped <strong>generating</strong> reports<br>and started <strong>declaring that a report should exist</strong>.</p><p>Instead of:</p><blockquote><p>&#8220;Generate report now&#8221;</p></blockquote><p>The app began to say:</p><blockquote><p>&#8220;A report of type X, with parameters Y, is required.&#8221;</p></blockquote><p>That declaration became an <strong>event</strong>, not a function call.</p><p>This is where Kafka entered the picture.</p><div><hr></div><h2>Why Kafka Was the Right Fit (Not a Queue, Not a Database)</h2><p>At first glance, Kafka felt like overkill.</p><p>But then the nature of reports became clearer.</p><p>A report request:</p><ul><li><p>is a fact (&#8220;this report was requested&#8221;)</p></li><li><p>may be processed later</p></li><li><p>may be processed multiple times</p></li><li><p>may be reprocessed if logic changes</p></li><li><p>must be auditable</p></li></ul><p>Kafka fits because it treats events as <strong>records of truth</strong>, not tasks to discard.</p><p>The .NET app publishes something like:</p><blockquote><p>&#8220;ReportRequested&#8221;</p></blockquote><p>And then&#8230; it&#8217;s done.</p><p>No waiting.<br>No heavy lifting.<br>No assumption about who processes it.</p><p>That separation alone reduced stress in the codebase.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h2>What the .NET Application Is Responsible For (and What It Is Not)</h2><p>This part matters.</p><p>The .NET application:</p><ul><li><p>validates input</p></li><li><p>determines report intent</p></li><li><p>publishes the event to Kafka</p></li><li><p>returns immediately to the user</p></li></ul><p>It does <strong>not</strong>:</p><ul><li><p>fetch all report data</p></li><li><p>format files</p></li><li><p>write to storage</p></li><li><p>worry about retries or failures</p></li></ul><p>This was uncomfortable at first.</p><p>Letting go of control always is.</p><p>But it made the app lighter, faster, and easier to reason about.</p><div><hr></div><h2>Kafka&#8217;s Role: The Unbiased Recorder</h2><p>Kafka does not care about reports.</p><p>That&#8217;s its strength.</p><p>Kafka simply records:</p><ul><li><p>report requested</p></li><li><p>with parameters</p></li><li><p>at this time</p></li><li><p>by this source</p></li></ul><p>It doesn&#8217;t:</p><ul><li><p>decide how to generate</p></li><li><p>care how long it takes</p></li><li><p>delete the message after consumption</p></li></ul><p>That means:</p><ul><li><p>multiple ingestion services can read the same request</p></li><li><p>failures don&#8217;t lose intent</p></li><li><p>logic can evolve without re-triggering users</p></li></ul><p>Kafka became the <strong>memory of intent</strong>.</p><div><hr></div><h2>Enter the Ingestion Service: Where Work Actually Happens</h2><p>This is where the real work lives.</p><p>The ingestion service is not a controller.<br>Not a UI.<br>Not a database.</p><p>It&#8217;s a <strong>specialized worker</strong> whose only responsibility is:</p><blockquote><p>&#8220;Turn report intent into report artifacts.&#8221;</p></blockquote><p>It:</p><ul><li><p>consumes report events from Kafka</p></li><li><p>fetches data from multiple sources</p></li><li><p>applies business rules</p></li><li><p>formats output (CSV, PDF, Excel, JSON)</p></li><li><p>handles retries and partial failures</p></li></ul><p>This service is allowed to be:</p><ul><li><p>slow</p></li><li><p>CPU-heavy</p></li><li><p>memory-heavy</p></li></ul><p>Because it&#8217;s isolated.</p><p>Failures here don&#8217;t take down the user-facing app.</p><p>That isolation was the biggest architectural win.</p><div><hr></div><h2>Why Ingestion Is a Separate Service (and Not Just a Background Job)</h2><p>At some point I asked:</p><blockquote><p>&#8220;Why not just run this as a background job in the same app?&#8221;</p></blockquote><p>The answer came from experience:</p><p>Because ingestion:</p><ul><li><p>scales differently</p></li><li><p>fails differently</p></li><li><p>evolves differently</p></li><li><p>needs different observability</p></li><li><p>has different SLAs</p></li></ul><p>Once reports were critical, ingestion became a <strong>system</strong>, not a helper.</p><p>Separating it allowed:</p><ul><li><p>independent scaling</p></li><li><p>independent deployments</p></li><li><p>independent retry strategies</p></li></ul><p>And that independence saved us more than once.</p><div><hr></div><h2>Shared Persistence: The Final Destination</h2><p>After generation, reports need a home.</p><p>Not inside memory.<br>Not inside the ingestion service.<br>Not inside Kafka.</p><p>They need <strong>shared persistence</strong>.</p><p>This is where blob storage (or shared file systems) comes in.</p><p>Blob storage is ideal because:</p><ul><li><p>it&#8217;s durable</p></li><li><p>it&#8217;s cheap</p></li><li><p>it&#8217;s scalable</p></li><li><p>it&#8217;s accessible by multiple consumers</p></li><li><p>it decouples generation from consumption</p></li></ul><p>Once a report is written to blob storage:</p><ul><li><p>users can download it later</p></li><li><p>other services can process it</p></li><li><p>analytics jobs can read it</p></li><li><p>retention policies can apply</p></li></ul><p>The report becomes a <strong>first-class artifact</strong>, not a temporary output.</p><div><hr></div><h2>The Flow, End to End (How It Feels in Practice)</h2><p>Here&#8217;s how the system <em>feels</em> when designed this way:</p><ol><li><p>User requests a report</p></li><li><p>.NET app validates and publishes intent to Kafka</p></li><li><p>Kafka records the request</p></li><li><p>Ingestion service consumes when ready</p></li><li><p>Report is generated asynchronously</p></li><li><p>Output is written to shared storage</p></li><li><p>Status updates can be published back if needed</p></li></ol><p>No blocking.<br>No guessing.<br>No hidden coupling.</p><p>Just a clean flow of responsibility.</p><p>&#128073; I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a></strong><br>(Instant download, production-ready, no fluff)</p><div><hr></div><h2>The Biggest Mental Shift This Flow Taught Me</h2><p>This architecture taught me something important:</p><blockquote><p><strong>Systems work better when intent, processing, and storage are separate concerns.</strong></p></blockquote><p>Trying to collapse them into one layer feels simpler &#8212;<br>until scale, failures, and change arrive.</p><p>Then separation stops feeling like ceremony<br>and starts feeling like relief.</p><div><hr></div><h2>Why This Design Ages Well</h2><p>This flow survives change because:</p><ul><li><p>report formats can change without UI changes</p></li><li><p>ingestion logic can evolve independently</p></li><li><p>storage policies can change without regeneration</p></li><li><p>new consumers can read old events</p></li><li><p>reprocessing is possible without user involvement</p></li></ul><p>That&#8217;s not over-engineering.</p><p>That&#8217;s designing for time.</p><div><hr></div><h2>Final Thought</h2><p>At first, this architecture feels indirect.</p><p>Why not just generate the report?</p><p>But once you live with it, you realize:</p><p>The complexity was always there.<br>You just moved it to the right places.</p><p>When the .NET app stops being a factory<br>and starts being a coordinator,<br>everything becomes calmer.</p><p>And calm systems are the ones that survive.</p>]]></content:encoded></item><item><title><![CDATA[Kafka, Service Bus, RabbitMQ, Message Queues & Ingestion Services]]></title><description><![CDATA[Twins? Siblings? Same Family? Or Just Neighbours in the Same City?]]></description><link>https://dotnetfullstackdev.substack.com/p/kafka-service-bus-rabbitmq-message</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/kafka-service-bus-rabbitmq-message</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sun, 01 Feb 2026 16:44:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KJuO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KJuO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KJuO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 424w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 848w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 1272w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KJuO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png" width="1358" height="738" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:738,&quot;width&quot;:1358,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Kafka vs RabbitMQ. A Comprehensive Comparison | by Sachin Kumar ...&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Kafka vs RabbitMQ. A Comprehensive Comparison | by Sachin Kumar ..." title="Kafka vs RabbitMQ. A Comprehensive Comparison | by Sachin Kumar ..." srcset="https://substackcdn.com/image/fetch/$s_!KJuO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 424w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 848w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 1272w, https://substackcdn.com/image/fetch/$s_!KJuO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84f1eb5f-18a7-4f02-a4d9-9f5dfc9e4211_1358x738.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every backend developer reaches this moment.</p><p>You&#8217;re designing a system.<br>Someone says:</p><blockquote><p>&#8220;Let&#8217;s put Kafka.&#8221;<br>Another says: &#8220;Service Bus is enough.&#8221;<br>Someone else: &#8220;RabbitMQ is simpler.&#8221;<br>And a senior casually drops: &#8220;This is just ingestion.&#8221;</p></blockquote><p>At this point, your brain doesn&#8217;t explode.<br>It <strong>freezes</strong>.</p><p>Because all of them:</p><ul><li><p>move messages</p></li><li><p>decouple systems</p></li><li><p>run in the background</p></li><li><p>sound interchangeable</p></li></ul><p>But they are <strong>not the same thing</strong>.</p><p>This blog will untangle them in a way you&#8217;ll <em>never confuse again</em> &#8212; using <strong>family metaphors, real system behavior, and .NET examples</strong>.</p><p>By the end, you&#8217;ll know:</p><ul><li><p>who belongs to the same family</p></li><li><p>who are siblings vs cousins</p></li><li><p>who are neighbors doing different jobs</p></li><li><p>and <strong>which one YOU should pick &#8212; and why</strong></p></li></ul><div><hr></div><h2>First, the big picture (this is the anchor)</h2><p>Before naming tools, understand this truth:</p><blockquote><p><strong>These systems exist to move data between systems </strong><em><strong>without forcing them to talk directly</strong></em><strong>.</strong></p></blockquote><p>That&#8217;s it.</p><p>But <strong>how</strong> they move data<br><strong>when</strong> they move data<br><strong>who remembers it</strong><br>and <strong>who guarantees delivery</strong><br>&#8212; that&#8217;s where they differ.</p><div><hr></div><h2>The &#8220;Family Tree&#8221; (mental model)</h2><p>Let&#8217;s group them correctly.</p><h3>The Big Family: <em>Asynchronous Messaging &amp; Streaming</em></h3><p>Inside this family, we have <strong>two major tribes</strong>:</p><ol><li><p><strong>Message Brokers (Queues &amp; Buses)</strong></p></li><li><p><strong>Event Streaming Platforms</strong></p></li></ol><p>And standing nearby (but not exactly family):</p><ol start="3"><li><p><strong>Ingestion Services</strong></p></li></ol><p>Now let&#8217;s meet each one properly.</p><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><h2>Message Queue &#8212; The Grandparent Concept</h2><p>A <strong>message queue</strong> is the <strong>idea</strong>, not a product.</p><p>It means:</p><ul><li><p>one system sends a message</p></li><li><p>another system processes it later</p></li><li><p>they don&#8217;t block each other</p></li></ul><p>Key characteristics:</p><ul><li><p>message is consumed <strong>once</strong></p></li><li><p>after consumption, it&#8217;s usually <strong>gone</strong></p></li><li><p>focus is on <strong>task execution</strong></p></li></ul><p>Think:</p><blockquote><p>&#8220;Do this job when you get time.&#8221;</p></blockquote><h3>Example scenario</h3><ul><li><p>User uploads a file</p></li><li><p>Backend enqueues &#8220;ProcessFile&#8221;</p></li><li><p>Worker processes it in background</p></li></ul><p>This idea existed long before Kafka or cloud.</p><p>In .NET, this concept shows up everywhere:</p><ul><li><p>Background workers</p></li><li><p>Hosted services</p></li><li><p>Queues</p></li><li><p>Even <code>Task.Run()</code> is a tiny cousin of this idea</p></li></ul><div><hr></div><h2>RabbitMQ &#8212; The Responsible Sibling</h2><p>RabbitMQ is a <strong>classic message broker</strong>.</p><p>It lives and breathes <strong>message queue semantics</strong>.</p><h3>What RabbitMQ is really good at</h3><ul><li><p>Command-style messages</p></li><li><p>Job processing</p></li><li><p>Reliable delivery</p></li><li><p>Acknowledgements</p></li><li><p>Routing (fanout, topic, direct)</p></li></ul><p>RabbitMQ thinks like this:</p><blockquote><p>&#8220;I must make sure this message is handled &#8212; exactly once if possible.&#8221;</p></blockquote><h3>Mental model</h3><p>RabbitMQ is like a <strong>post office</strong>:</p><ul><li><p>letters arrive</p></li><li><p>post office routes them</p></li><li><p>once delivered, letter is done</p></li></ul><h3>Simple .NET producer example (conceptual)</h3><pre><code><code>var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare("orders");

var message = Encoding.UTF8.GetBytes("OrderCreated");

channel.BasicPublish(
    exchange: "",
    routingKey: "orders",
    body: message
);
</code></code></pre><h3>Consumer</h3><pre><code><code>channel.BasicConsume("orders", autoAck: false, consumer);
</code></code></pre><p>RabbitMQ:</p><ul><li><p>expects consumers to <strong>ack</strong></p></li><li><p>retries if they fail</p></li><li><p>cares deeply about delivery guarantees</p></li></ul><div><hr></div><h2>Azure Service Bus &#8212; The Enterprise Cousin</h2><p>Azure Service Bus is <strong>not just a queue</strong>.</p><p>It&#8217;s a <strong>managed enterprise messaging platform</strong>.</p><p>If RabbitMQ is a post office,<br>Service Bus is a <strong>corporate mailroom with rules</strong>.</p><h3>What Service Bus adds</h3><ul><li><p>Queues <strong>and</strong> Topics</p></li><li><p>Dead-letter queues</p></li><li><p>Scheduled messages</p></li><li><p>Sessions (ordered processing)</p></li><li><p>Transactions</p></li><li><p>Azure-native security &amp; scaling</p></li></ul><p>It&#8217;s designed for:</p><ul><li><p>enterprise workflows</p></li><li><p>business processes</p></li><li><p>financial systems</p></li><li><p>regulated environments</p></li></ul><h3>Mental model</h3><blockquote><p>&#8220;This message is part of a business process &#8212; treat it carefully.&#8221;</p></blockquote><h3>.NET example (simplified)</h3><pre><code><code>var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender("orders");

await sender.SendMessageAsync(
    new ServiceBusMessage("OrderCreated")
);
</code></code></pre><p>Service Bus <strong>does not want to be fast above all</strong>.<br>It wants to be <strong>correct, secure, and auditable</strong>.</p><div><hr></div><h2>Kafka &#8212; The Loud, Fast, Memory-Rich Sibling</h2><p>Kafka is <strong>not a message queue</strong> &#8212; even though people use it like one.</p><p>Kafka belongs to a <strong>different tribe</strong>.</p><p>Kafka is an <strong>event streaming platform</strong>.</p><h3>How Kafka thinks</h3><p>Kafka doesn&#8217;t ask:</p><blockquote><p>&#8220;Did someone process this message?&#8221;</p></blockquote><p>Kafka asks:</p><blockquote><p>&#8220;I will record this fact forever. Others can read it when they want.&#8221;</p></blockquote><h3>Core differences (intuitively)</h3><ul><li><p>Messages are <strong>not deleted after consumption</strong></p></li><li><p>Consumers track their <strong>own offset</strong></p></li><li><p>Multiple consumers can read the <strong>same data</strong></p></li><li><p>Data is replayable</p></li><li><p>Order matters deeply</p></li></ul><p>Kafka is like a <strong>public event log</strong>:</p><blockquote><p>&#8220;These events happened. Deal with it.&#8221;</p></blockquote><h3>Example Kafka mindset</h3><ul><li><p>UserCreated event</p></li><li><p>OrderPlaced event</p></li><li><p>PaymentFailed event</p></li></ul><p>These are <strong>facts</strong>, not commands.</p><h3>.NET producer (conceptual)</h3><pre><code><code>var config = new ProducerConfig { BootstrapServers = "localhost:9092" };

using var producer = new ProducerBuilder&lt;Null, string&gt;(config).Build();

await producer.ProduceAsync(
    "orders",
    new Message&lt;Null, string&gt; { Value = "OrderPlaced" }
);
</code></code></pre><p>Kafka shines when:</p><ul><li><p>many systems need the same data</p></li><li><p>replay is required</p></li><li><p>analytics + services coexist</p></li><li><p>throughput is massive</p></li></ul><div><hr></div><h2>Ingestion Services &#8212; The Neighbours (Not Family)</h2><p>Ingestion services are <strong>not brokers</strong>.</p><p>They are <strong>pipelines</strong>.</p><p>Their job:</p><ul><li><p>receive data</p></li><li><p>transform/validate/enrich</p></li><li><p>forward to storage or streams</p></li></ul><p>Examples:</p><ul><li><p>log ingestion</p></li><li><p>metrics ingestion</p></li><li><p>telemetry pipelines</p></li><li><p>ETL-lite services</p></li></ul><h3>Mental model</h3><blockquote><p>&#8220;I don&#8217;t store messages. I <em>process flow</em>.&#8221;</p></blockquote><p>Often:</p><ul><li><p>Kafka feeds ingestion</p></li><li><p>Ingestion feeds databases</p></li><li><p>Ingestion feeds analytics</p></li></ul><p>In .NET, ingestion often looks like:</p><ul><li><p>background services</p></li><li><p>streaming processors</p></li><li><p>consumers + transformers</p></li></ul><div><hr></div><h2>How they relate (final clarity)</h2><h3>Are they twins?</h3><p>&#10060; No.</p><h3>Are they siblings?</h3><p>&#10004;&#65039; RabbitMQ &amp; Service Bus are siblings<br>&#10004;&#65039; Kafka is from the same family but <strong>different tribe</strong></p><h3>Same family?</h3><p>&#10004;&#65039; Yes &#8212; asynchronous systems</p><h3>Neighbours?</h3><p>&#10004;&#65039; Ingestion services are neighbours &#8212; they <em>use</em> brokers, they are not brokers</p><div><hr></div><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><h2>Other similar concepts in the .NET world</h2><h3>1&#65039;&#8419; Background Workers</h3><ul><li><p><code>IHostedService</code></p></li><li><p><code>BackgroundService</code></p></li><li><p>In-process queues</p></li></ul><p>Good for:</p><ul><li><p>small workloads</p></li><li><p>internal async tasks</p></li></ul><p>Not good for:</p><ul><li><p>distributed systems</p></li><li><p>scaling independently</p></li></ul><div><hr></div><h3>2&#65039;&#8419; Event Sourcing</h3><ul><li><p>Often built <strong>on top of Kafka</strong></p></li><li><p>Events are source of truth</p></li><li><p>Read models built separately</p></li></ul><p>Kafka fits this naturally.</p><div><hr></div><h3>3&#65039;&#8419; SignalR</h3><ul><li><p>Not a queue</p></li><li><p>Not streaming</p></li><li><p>Real-time push to clients</p></li></ul><p>Neighbour, not family.</p><div><hr></div><h3>4&#65039;&#8419; Azure Event Grid</h3><ul><li><p>Event notification system</p></li><li><p>Fire-and-forget</p></li><li><p>No replay</p></li><li><p>No ordering guarantee</p></li></ul><p>Good for:</p><ul><li><p>reacting to cloud events</p></li><li><p>glue between services</p></li></ul><div><hr></div><h2>How to choose (the honest guidance)</h2><p>Ask <strong>one question</strong>:</p><blockquote><p>&#8220;Am I sending a <strong>command</strong> or publishing a <strong>fact</strong>?&#8221;</p></blockquote><h3>If it&#8217;s a command:</h3><ul><li><p>&#8220;Process this&#8221;</p></li><li><p>&#8220;Send this email&#8221;</p></li><li><p>&#8220;Charge this payment&#8221;</p></li></ul><p>&#8594; <strong>RabbitMQ / Service Bus</strong></p><h3>If it&#8217;s a fact:</h3><ul><li><p>&#8220;Order was placed&#8221;</p></li><li><p>&#8220;User logged in&#8221;</p></li><li><p>&#8220;Payment failed&#8221;</p></li></ul><p>&#8594; <strong>Kafka</strong></p><h3>If it&#8217;s transformation flow:</h3><ul><li><p>logs</p></li><li><p>metrics</p></li><li><p>telemetry</p></li></ul><p>&#8594; <strong>Ingestion service (often backed by Kafka)</strong></p><div><hr></div><h2>The one-line memory trick</h2><p>If you remember nothing else, remember this:</p><blockquote><p><strong>Queues ask: &#8220;Did you do it?&#8221;<br>Kafka says: &#8220;This happened.&#8221;</strong></p></blockquote><p>That sentence alone separates 80% of confusion.</p><div><hr></div><blockquote><p>&#128073;  I&#8217;ve shared the <strong><a href="https://dotnetfullstackdev.gumroad.com/l/vesec">JWT Authentication Boilerplate for ASP.NET Core (.NET 8)</a></strong> <br>(Instant download, production-ready, no fluff) </p></blockquote><blockquote><div><hr></div></blockquote><h2>Final closing</h2><p>Most teams don&#8217;t fail because they picked the &#8220;wrong&#8221; tool.</p><p>They fail because:</p><ul><li><p>they didn&#8217;t understand the <em>nature</em> of their data</p></li><li><p>they treated facts like commands</p></li><li><p>or commands like facts</p></li></ul><p>Once you see these systems as <strong>roles in a family</strong>, not interchangeable tools, your designs become calm, intentional, and scalable.</p><p>And that&#8217;s when you stop being &#8220;the dev who knows Kafka&#8221;<br>and become <strong>the architect who knows why</strong>.</p>]]></content:encoded></item><item><title><![CDATA[The Day .NET Started Feeling Different]]></title><description><![CDATA[A Quiet Story About How Junior Developers Become Experts]]></description><link>https://dotnetfullstackdev.substack.com/p/the-day-net-started-feeling-different</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/the-day-net-started-feeling-different</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sat, 31 Jan 2026 15:11:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!K3zR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It usually doesn&#8217;t happen during an interview.<br>It doesn&#8217;t happen when you get promoted.<br>And it definitely doesn&#8217;t happen when you finish another course.</p><p>For most developers, the shift from junior to expert happens on an <strong>ordinary day</strong>, in front of an ordinary screen, staring at code they&#8217;ve written a hundred times before &#8212; and suddenly realizing:</p><blockquote><p><em>&#8220;I don&#8217;t actually understand what&#8217;s happening here.&#8221;</em></p></blockquote><p>That moment is uncomfortable.<br>But it&#8217;s also the beginning of something important.</p><div class="pullquote"><p><br><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><h2>Chapter 1: When &#8220;Learning .NET&#8221; Stops Feeling Like Progress</h2><p>Early in your career, learning .NET feels exciting.</p><p>You learn:</p><ul><li><p>Controllers</p></li><li><p>Services</p></li><li><p>Dependency Injection</p></li><li><p>async/await</p></li><li><p>Entity Framework</p></li></ul><p>Every new concept feels like a level unlocked.</p><p>Your code compiles.<br>Your APIs respond.<br>Your manager is happy.</p><p>But one day, something strange happens.</p><p>The code still works&#8230;<br>&#8230;but it no longer feels <em>solid</em>.</p><p>You deploy a change and something unrelated breaks.<br>A small feature takes days to debug.<br>Performance issues appear without obvious reason.</p><p>You realize you&#8217;re not stuck because you don&#8217;t know enough.</p><p>You&#8217;re stuck because you don&#8217;t <strong>see the system yet</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K3zR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K3zR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K3zR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2762962,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/186413783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K3zR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!K3zR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe04678f8-f756-480e-89e2-cebe030b4fd9_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Chapter 2: The First Time async/await Betrays You</h2><p>You remember the day you learned async/await.</p><p>It felt magical.<br>No more blocking.<br>Everything faster.</p><p>So you used it everywhere.</p><p>And then one day, production slows down.</p><p>Requests hang.<br>CPU is fine.<br>Memory looks okay.</p><p>Logs don&#8217;t scream.<br>They whisper.</p><p>You stare at the code and think:</p><blockquote><p><em>&#8220;But I followed the rules&#8230;&#8221;</em></p></blockquote><p>That&#8217;s the day async/await stops being syntax and starts becoming <strong>behavior</strong>.</p><p>You begin to imagine threads being released.<br>Continuations waiting quietly.<br>The thread pool slowly starving.</p><p>You stop asking:</p><blockquote><p>&#8220;Where should I put await?&#8221;</p></blockquote><p>And start asking:</p><blockquote><p>&#8220;What is the runtime doing right now?&#8221;</p></blockquote><p>That&#8217;s not learning anymore.<br>That&#8217;s <strong>experience being carved into your thinking</strong>.</p><div><hr></div><h2>Chapter 3: The God Service You Were Afraid to Touch</h2><p>There&#8217;s always that one service.</p><p>Huge.<br>Scary.<br>Everyone avoids it.</p><p>You didn&#8217;t plan to create it.<br>You just kept adding &#8220;one more thing&#8221;.</p><p>Validation went there.<br>Business rules went there.<br>Email sending went there.</p><p>Now, every change feels risky.</p><p>You open the file and your instinct says:</p><blockquote><p><em>&#8220;Close it before you break something.&#8221;</em></p></blockquote><p>That fear teaches you something tutorials never did:</p><p>Responsibilities matter more than features.</p><p>You realize Dependency Injection wasn&#8217;t meant to make things flexible.<br>It was meant to make <strong>change survivable</strong>.</p><p>From that day on, you stop asking:</p><blockquote><p>&#8220;Can I inject this?&#8221;</p></blockquote><p>And start asking:</p><blockquote><p>&#8220;Who should own this decision?&#8221;</p></blockquote><p>That&#8217;s architecture entering your bloodstream.</p><div><hr></div><h2>Chapter 4: The Database Stops Feeling Like Storage</h2><p>At first, the database was just there.</p><p>You queried it.<br>You updated rows.<br>You fixed bugs with WHERE clauses.</p><p>Until the day data was wrong.</p><p>Not slow.<br>Not missing.<br>Wrong.</p><p>Balances didn&#8217;t match.<br>Reports disagreed.<br>History couldn&#8217;t be trusted.</p><p>You realize something quietly terrifying:</p><blockquote><p><em>Once data lies, nothing above it can be trusted.</em></p></blockquote><p>That&#8217;s when you stop treating the database as storage<br>and start treating it as a <strong>contract with reality</strong>.</p><p>You think about:</p><ul><li><p>Invariants</p></li><li><p>Ownership</p></li><li><p>What should never change</p></li><li><p>What must be traceable forever</p></li></ul><p>You stop asking:</p><blockquote><p>&#8220;How do I fetch this?&#8221;</p></blockquote><p>And start asking:</p><blockquote><p>&#8220;Why does this data exist like this?&#8221;</p></blockquote><p>That&#8217;s not SQL knowledge.<br>That&#8217;s <strong>system thinking</strong>.</p><div><hr></div><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><h2>Chapter 5: Performance Stops Being About Speed</h2><p>Junior you thought performance was about speed.</p><p>Loops.<br>Indexes.<br>Caching.</p><p>Expert you knows performance is about <strong>cost</strong>.</p><p>You&#8217;ve seen systems where:</p><ul><li><p>Caching made things worse</p></li><li><p>Optimization hid deeper problems</p></li><li><p>GC pauses killed throughput</p></li></ul><p>You stop touching code first.</p><p>You sit back.<br>You measure.<br>You observe.</p><p>You ask:</p><ul><li><p>Is this IO or CPU?</p></li><li><p>Why is this path hot?</p></li><li><p>Can we reduce work instead of speeding it up?</p></li></ul><p>You realize most performance issues were design decisions made long ago.</p><p>And suddenly, performance becomes calm instead of frantic.</p><div><hr></div><h2>Chapter 6: Errors Become Expected Guests, Not Surprises</h2><p>There was a time when errors scared you.</p><p>Exceptions meant failure.<br>Failure meant panic.</p><p>Then you got paged at night.</p><p>And you learned the truth:</p><p>Failures are not accidents.<br>They are <strong>part of the system&#8217;s life</strong>.</p><p>You stop wrapping everything in try-catch.<br>You stop hiding errors.</p><p>You design failure paths.</p><p>You decide:</p><ul><li><p>What can retry</p></li><li><p>What must stop</p></li><li><p>What must alert humans</p></li><li><p>What must fail loudly</p></li></ul><p>You don&#8217;t ask:</p><blockquote><p>&#8220;Did we handle the exception?&#8221;</p></blockquote><p>You ask:</p><blockquote><p>&#8220;Did we handle reality?&#8221;</p></blockquote><p>That&#8217;s experience speaking.</p><div><hr></div><h2>Chapter 7: Debugging Becomes Storytelling</h2><p>You no longer debug line by line.</p><p>You tell stories.</p><p>&#8220;What happened first?&#8221;<br>&#8220;What assumption failed?&#8221;<br>&#8220;How did state evolve?&#8221;</p><p>You replay the system in your head like a movie.</p><p>And most bugs suddenly make sense.</p><p>Because bugs were never random.</p><p>They were consequences.</p><div><hr></div><h2>Chapter 8: The Quiet Confidence of an Expert</h2><p>One day, someone asks you a question in a meeting.</p><p>You don&#8217;t rush to answer.</p><p>You think.</p><p>You explain not just what you chose &#8212; but <strong>why</strong>.</p><p>You talk about trade-offs.<br>You mention risks.<br>You admit uncertainty.</p><p>And people listen.</p><p>Not because you know everything &#8212;<br>but because your thinking is visible.</p><p>That&#8217;s when you realize:</p><p>Expertise is not loud.<br>It&#8217;s calm.</p><div><hr></div><h2>Final Chapter: When Learning Turns Into Judgment</h2><p>Experts don&#8217;t read more than juniors.<br>They reread basics with new eyes.</p><p>They don&#8217;t chase every new framework.<br>They deepen their understanding of fundamentals.</p><p>They don&#8217;t avoid mistakes.<br>They extract meaning from them.</p><p>And one day, without ceremony, you realize:</p><blockquote><p><em>&#8220;.NET didn&#8217;t change.<br>I did.&#8221;</em></p></blockquote><div><hr></div><h2>A Quiet Ending</h2><p>If you&#8217;re a junior .NET developer reading this:</p><p>You don&#8217;t need to rush.<br>You don&#8217;t need to prove anything.<br>You don&#8217;t need to know everything.</p><p>Just pay attention.</p><p>To behavior.<br>To consequences.<br>To the moments when things break and you learn why.</p><p>That&#8217;s how learning turns into experience.</p><p>And that&#8217;s how developers become experts &#8212;<br>not overnight, but inevitably.</p>]]></content:encoded></item><item><title><![CDATA[From Idea to Code: Low-Level Design of a SettleUp-Like App (.NET Core + React + SQL)]]></title><description><![CDATA[For content overview videos]]></description><link>https://dotnetfullstackdev.substack.com/p/from-idea-to-code-low-level-design</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/from-idea-to-code-low-level-design</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Mon, 26 Jan 2026 14:09:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!OqtV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>If you&#8217;re a .NET developer, this blog is the missing bridge between:</p><ul><li><p>system-design interviews</p></li><li><p>and real production code</p></li></ul><p>We&#8217;ll design a <strong>SettleUp-like expense-splitting app</strong>, end to end:</p><ul><li><p>backend in <strong>.NET Core</strong></p></li><li><p>frontend in <strong>React</strong></p></li><li><p>database in <strong>SQL</strong></p></li></ul><p>And more importantly, we&#8217;ll explain <strong>why this stack makes sense</strong> &#8212; not because it&#8217;s popular, but because it fits the problem.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OqtV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OqtV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OqtV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg" width="1000" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39264,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/185842061?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OqtV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!OqtV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d40ba17-eefd-4c8f-b382-38e11e75ac3f_1000x500.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>Why this tech stack (before we design anything)</h2><p>Let&#8217;s answer the &#8220;why&#8221; upfront, because architecture always starts with constraints.</p><h3>Why .NET Core for backend?</h3><p>Because this problem needs:</p><ul><li><p>strong domain modeling</p></li><li><p>clear separation of concerns</p></li><li><p>predictable performance</p></li><li><p>long-term maintainability</p></li></ul><p>.NET Core gives us:</p><ul><li><p>clean layering (Domain, Application, Infrastructure)</p></li><li><p>excellent async handling (important for APIs)</p></li><li><p>strong typing for financial logic</p></li><li><p>mature ecosystem for auth, logging, validation</p></li></ul><p>This is <strong>business logic-heavy software</strong>, not a script.<br>.NET shines here.</p><div><hr></div><h3>Why React for frontend?</h3><p>SettleUp-style apps are:</p><ul><li><p>highly interactive</p></li><li><p>state-driven</p></li><li><p>real-time feeling (balances update instantly)</p></li><li><p>UI-heavy</p></li></ul><p>React fits because:</p><ul><li><p>UI is just a function of state</p></li><li><p>balance recalculations reflect immediately</p></li><li><p>components map cleanly to domain concepts</p></li><li><p>ecosystem supports charts, lists, notifications easily</p></li></ul><p>This isn&#8217;t a static website.<br>It&#8217;s a <strong>living financial dashboard</strong>.</p><div><hr></div><h3>Why SQL database?</h3><p>This is the most important choice.</p><p>Expense sharing is <strong>financial data</strong>.</p><p>That means:</p><ul><li><p>correctness &gt; flexibility</p></li><li><p>consistency &gt; speed hacks</p></li><li><p>auditability &gt; schemaless freedom</p></li></ul><p>SQL gives us:</p><ul><li><p>transactions</p></li><li><p>referential integrity</p></li><li><p>constraints</p></li><li><p>predictable queries</p></li><li><p>easy reconciliation</p></li></ul><p>Ledger-style systems and relational databases are a natural fit.</p><div><hr></div><h2>Big picture: how the system is split (low-level view)</h2><p>We split the system into <strong>three responsibilities</strong>:</p><ol><li><p><strong>Frontend (React)</strong></p><ul><li><p>collects user intent</p></li><li><p>displays derived data (balances, settlements)</p></li></ul></li><li><p><strong>Backend (.NET Core API)</strong></p><ul><li><p>enforces business rules</p></li><li><p>computes balances</p></li><li><p>guarantees correctness</p></li></ul></li><li><p><strong>Database (SQL)</strong></p><ul><li><p>stores immutable financial history</p></li><li><p>never lies</p></li></ul></li></ol><p>Frontend never calculates money.<br>Backend never trusts frontend numbers.<br>Database never stores &#8220;derived&#8221; values blindly.</p><p>This separation saves you from nightmares later.</p><div><hr></div><h2>Backend low-level design (.NET Core)</h2><p>Let&#8217;s go layer by layer &#8212; the way you&#8217;d actually structure the solution.</p><div><hr></div><h3>1&#65039;&#8419; API Layer (Controllers / Endpoints)</h3><p>This layer does <strong>one job only</strong>:</p><ul><li><p>accept requests</p></li><li><p>validate shape</p></li><li><p>call application services</p></li><li><p>return responses</p></li></ul><p>It must <strong>not</strong>:</p><ul><li><p>calculate balances</p></li><li><p>apply business rules</p></li><li><p>talk directly to EF entities</p></li></ul><p>Example responsibilities:</p><ul><li><p><code>POST /groups</code></p></li><li><p><code>POST /expenses</code></p></li><li><p><code>GET /groups/{id}/balances</code></p></li><li><p><code>POST /settlements</code></p></li></ul><p>Controllers should feel <em>boring</em>.<br>That&#8217;s a good sign.</p><div><hr></div><h3>2&#65039;&#8419; Application Layer (Use-Cases)</h3><p>This is where <strong>intent becomes action</strong>.</p><p>Each use-case represents:</p><ul><li><p>a business action</p></li><li><p>a user intention</p></li></ul><p>Examples:</p><ul><li><p>CreateExpense</p></li><li><p>AddGroupMember</p></li><li><p>RecordSettlement</p></li><li><p>GetGroupBalances</p></li><li><p>GetSimplifiedSettlements</p></li></ul><p>Each use-case:</p><ul><li><p>validates business rules</p></li><li><p>coordinates domain objects</p></li><li><p>controls transactions</p></li></ul><p>Think of this layer as:</p><blockquote><p>&#8220;The orchestrator of business workflows&#8221;</p></blockquote><div><hr></div><h3>3&#65039;&#8419; Domain Layer (the heart of correctness)</h3><p>This is the most important part of SettleUp.</p><p>Here you model:</p><ul><li><p>Expense</p></li><li><p>Split</p></li><li><p>Settlement</p></li><li><p>Balance calculation rules</p></li></ul><p>Key design principle:</p><blockquote><p><strong>Domain objects protect invariants.</strong></p></blockquote><p>For example:</p><ul><li><p>Total splits must equal total expense</p></li><li><p>Settlement amount must be positive</p></li><li><p>A user cannot owe themselves</p></li><li><p>Currency mismatch must be explicit</p></li></ul><p>If these rules leak outside the domain, bugs creep in silently.</p><div><hr></div><h3>4&#65039;&#8419; Balance calculation service (core logic)</h3><p>This deserves special attention.</p><p>You do <strong>not</strong> store balances permanently.</p><p>Instead:</p><ul><li><p>fetch all ledger entries (expenses + settlements)</p></li><li><p>compute net balances per user</p></li><li><p>simplify debts if required</p></li></ul><p>This logic must be:</p><ul><li><p>deterministic</p></li><li><p>testable</p></li><li><p>independent of database concerns</p></li></ul><p>This is where interviews are won and production bugs are avoided.</p><div><hr></div><h3>5&#65039;&#8419; Infrastructure Layer (EF Core + SQL)</h3><p>This layer:</p><ul><li><p>persists domain entities</p></li><li><p>maps objects to tables</p></li><li><p>manages transactions</p></li></ul><p>Important design choice:</p><ul><li><p>expenses and settlements are <strong>append-only</strong></p></li><li><p>updates create new records or mark old ones as inactive</p></li></ul><p>Why?<br>Because financial history must be explainable.</p><p>Never &#8220;rewrite&#8221; money.</p><div><hr></div><h2>Database low-level design (SQL mindset)</h2><p>The database represents <strong>truth</strong>, not convenience.</p><div><hr></div><h3>Ledger-first storage</h3><p>Instead of:</p><ul><li><p>storing &#8220;A owes B &#8377;500&#8221;</p></li></ul><p>We store:</p><ul><li><p>who paid</p></li><li><p>who owed</p></li><li><p>who settled</p></li><li><p>when</p></li><li><p>how much</p></li></ul><p>Balances are <strong>derived views</strong>, not stored facts.</p><p>This enables:</p><ul><li><p>recomputation</p></li><li><p>audits</p></li><li><p>debugging disputes</p></li><li><p>future rule changes</p></li></ul><div><hr></div><h3>Transactions everywhere</h3><p>Any operation that:</p><ul><li><p>creates an expense</p></li><li><p>records a settlement</p></li></ul><p>must be wrapped in a transaction.</p><p>If one insert fails, <strong>nothing should persist</strong>.</p><p>Money hates partial success.</p><div><hr></div><h3>Constraints &gt; Code checks</h3><p>Use SQL constraints for:</p><ul><li><p>foreign keys</p></li><li><p>non-negative amounts</p></li><li><p>required relationships</p></li></ul><p>This protects you even if:</p><ul><li><p>someone bypasses the API</p></li><li><p>a bug slips into code</p></li></ul><div><hr></div><h2>Frontend low-level design (React)</h2><p>Now let&#8217;s move to the UI &#8212; where things <em>feel</em> complex.</p><div><hr></div><h3>State-driven UI (not imperative UI)</h3><p>In React, the UI is:</p><blockquote><p>a projection of state</p></blockquote><p>That means:</p><ul><li><p>no manual DOM updates</p></li><li><p>no recalculations in UI</p></li><li><p>backend sends truth</p></li><li><p>frontend renders truth</p></li></ul><p>Example:</p><ul><li><p>user adds expense</p></li><li><p>API returns updated balances</p></li><li><p>React re-renders automatically</p></li></ul><p>Frontend should never &#8220;guess&#8221; balances.</p><div><hr></div><h3>Component structure (domain-aligned)</h3><p>Good React apps mirror backend concepts:</p><ul><li><p>GroupList</p></li><li><p>GroupDetails</p></li><li><p>ExpenseList</p></li><li><p>AddExpenseForm</p></li><li><p>BalanceSummary</p></li><li><p>SettlementSuggestions</p></li></ul><p>When frontend names match backend concepts:</p><ul><li><p>bugs reduce</p></li><li><p>onboarding is faster</p></li><li><p>communication improves</p></li></ul><div><hr></div><h3>API contract discipline</h3><p>Frontend talks to backend via:</p><ul><li><p>DTOs</p></li><li><p>well-defined response models</p></li></ul><p>Never expose EF entities directly.</p><p>Frontend should not depend on:</p><ul><li><p>internal IDs</p></li><li><p>database structure</p></li><li><p>backend refactoring</p></li></ul><p>This decoupling is what allows change without pain.</p><div><hr></div><h2>Why this LLD scales (even if the app grows)</h2><p>This design supports:</p><ul><li><p>thousands of expenses</p></li><li><p>many groups</p></li><li><p>multiple currencies</p></li><li><p>future features (notifications, analytics)</p></li></ul><p>Because:</p><ul><li><p>ledger is immutable</p></li><li><p>business logic is centralized</p></li><li><p>UI is stateless relative to calculations</p></li><li><p>derived data is recalculated, not guessed</p></li></ul><p>You don&#8217;t paint yourself into a corner.</p><div><hr></div><h2>Common mistakes this design avoids</h2><p>Let&#8217;s be honest &#8212; these are mistakes many devs make:</p><ul><li><p>&#10060; calculating balances in frontend</p></li><li><p>&#10060; storing &#8220;who owes whom&#8221; directly</p></li><li><p>&#10060; mixing EF entities in controllers</p></li><li><p>&#10060; updating past financial records</p></li><li><p>&#10060; trusting UI calculations</p></li></ul><p>This design deliberately avoids all of them.</p><div><hr></div><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><h2>Final takeaway (the one to remember)</h2><p>If you remember only one thing from this blog, remember this:</p><blockquote><p><strong>SettleUp is not a CRUD app.<br>It&#8217;s a ledger-driven financial system with a friendly UI.</strong></p></blockquote><p>When you design it like a ledger:</p><ul><li><p>correctness becomes natural</p></li><li><p>edge cases become manageable</p></li><li><p>scaling becomes predictable</p></li><li><p>interviews become easier</p></li><li><p>production bugs reduce drastically</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Role-Based Authorization in ASP.NET Core Web API]]></title><description><![CDATA[What happens after authentication and before/while authorization &#8212; step by step (with clear code)]]></description><link>https://dotnetfullstackdev.substack.com/p/role-based-authorization-in-aspnet</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/role-based-authorization-in-aspnet</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Fri, 26 Dec 2025 01:09:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!t7wb!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37b06a29-ed02-4a54-a1ab-9ffa364e322f_148x148.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>You&#8217;ve already completed <strong>authentication</strong> (for example, JWT Bearer, Windows Auth, or Identity Provider).<br>Now the request reaches your ASP.NET Core API with a valid identity.</p><p>The next big question is:</p><blockquote><p>&#8220;How does .NET Core check roles and decide whether to allow this request?&#8221;</p></blockquote><p>This blog explains the <strong>exact pipeline</strong>: what happens <em>after authentication succeeds</em>, how roles are created and attached to the user, and how ASP.NET Core enforces role-based access.</p><div><hr></div><h2>1) The moment authentication completes: what you now have</h2><p>After authentication succeeds, ASP.NET Core has one key thing:</p><p><code>HttpContext.User</code> &#8212; a <code>ClaimsPrincipal</code></p><p>Think of it as an in-memory &#8220;passport&#8221; for this request. It contains:</p><ul><li><p><code>Identity.IsAuthenticated = true</code></p></li><li><p>A set of <strong>claims</strong> (name, email, subject id, tenant id, roles, etc.)</p></li></ul><p>At this stage, no endpoint access decision is made yet.<br>Authentication only answers: <strong>&#8220;Who is this?&#8221;</strong></p><div><hr></div><h2>2) Where roles live in ASP.NET Core: roles are just claims</h2><p>In ASP.NET Core, a <strong>role</strong> is usually represented as a claim:</p><ul><li><p>claim type: <code>ClaimTypes.Role</code> (or <code>"role"</code> / <code>"roles"</code> depending on your token)</p></li><li><p>claim value: <code>"Admin"</code>, <code>"Manager"</code>, <code>"Auditor"</code>, etc.</p></li></ul><p>So when people say &#8220;role-based authorization&#8221;, what they really mean is:</p><blockquote><p>&#8220;Authorization based on a specific claim that represents role membership.&#8221;</p></blockquote><p>This matters because your system might authenticate users correctly, but if roles are not mapped into the right claim type, <code>[Authorize(Roles="...")]</code> won&#8217;t work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!edTE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!edTE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 424w, https://substackcdn.com/image/fetch/$s_!edTE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 848w, https://substackcdn.com/image/fetch/$s_!edTE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 1272w, https://substackcdn.com/image/fetch/$s_!edTE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!edTE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png" width="657" height="273" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:273,&quot;width&quot;:657,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30378,&quot;alt&quot;:&quot;https://www.youtube.com/@DotNetFullstackDev&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/182599136?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="https://www.youtube.com/@DotNetFullstackDev" title="https://www.youtube.com/@DotNetFullstackDev" srcset="https://substackcdn.com/image/fetch/$s_!edTE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 424w, https://substackcdn.com/image/fetch/$s_!edTE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 848w, https://substackcdn.com/image/fetch/$s_!edTE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 1272w, https://substackcdn.com/image/fetch/$s_!edTE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4ed4b77-6544-4b7b-baa1-664d011a4adf_657x273.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://www.youtube.com/@DotNetFullstackDev</figcaption></figure></div><div><hr></div><h2>3) The request pipeline: where authorization happens</h2><p>Here&#8217;s the flow in ASP.NET Core:</p><ol><li><p>Request enters middleware pipeline</p></li><li><p><code>UseAuthentication()</code> runs</p><ul><li><p>builds <code>HttpContext.User</code></p></li></ul></li><li><p>Endpoint routing identifies which action is being called</p></li><li><p><code>UseAuthorization()</code> runs</p><ul><li><p>checks <code>[Authorize]</code>, roles, policies</p></li></ul></li><li><p>If allowed &#8594; controller action executes</p></li><li><p>If not allowed &#8594; 401 or 403 returned</p></li></ol><h3>The critical middleware order</h3><pre><code><code>app.UseRouting();

app.UseAuthentication();  // builds HttpContext.User (identity + claims)
app.UseAuthorization();   // enforces [Authorize] attributes

app.MapControllers();
</code></code></pre><p>If this order is wrong, role authorization will behave unpredictably.</p><div><hr></div><h2>4) Step-by-step: how ASP.NET decides &#8220;401 vs 403&#8221;</h2><p>This is one of the most confusing parts for juniors.</p><h3>401 Unauthorized</h3><p>Means: user is <strong>not authenticated</strong>.</p><ul><li><p>No valid token</p></li><li><p>Token expired</p></li><li><p>Signature invalid</p></li><li><p>Auth scheme not satisfied</p></li></ul><h3>403 Forbidden</h3><p>Means: user is authenticated, but <strong>not authorized</strong>.</p><ul><li><p>Token is valid</p></li><li><p>User is known</p></li><li><p>But role requirement failed</p></li></ul><p>Role-based authorization mostly leads to <strong>403</strong> (because authentication is already done).</p><div><hr></div><h2>5) Implementing role authorization using JWT (most common)</h2><h3>Step 1: Add JWT authentication</h3><pre><code><code>using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =&gt;
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "your-issuer",
            ValidAudience = "your-audience",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("super-secret-key-1234567890"))
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddControllers();

var app = builder.Build();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();
</code></code></pre><p>Authentication now creates <code>HttpContext.User</code>.</p><p>But role enforcement depends on the role claim being correct.</p><div><hr></div><h2>6) Step-by-step: ensuring roles are recognized correctly</h2><p>JWT tokens might store roles in different claim types, such as:</p><ul><li><p><code>"role"</code></p></li><li><p><code>"roles"</code></p></li><li><p><code>"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"</code></p></li></ul><p>ASP.NET Core checks role claims using the identity&#8217;s <code>RoleClaimType</code>.</p><p>If your token uses <code>"roles"</code> but ASP.NET expects <code>ClaimTypes.Role</code>, you must map it.</p><h3>Fix: set RoleClaimType</h3><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><pre><code><code>options.TokenValidationParameters = new TokenValidationParameters
{
    // other validations...
    RoleClaimType = "roles"
};
</code></code></pre><p>Now <code>[Authorize(Roles="Admin")]</code> will work when the token contains:</p><pre><code><code>"roles": ["Admin", "Auditor"]
</code></code></pre><div><hr></div><h2>7) Add role-based protection to endpoints</h2><h3>Controller example</h3><pre><code><code>using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ReportsController : ControllerBase
{
    [HttpGet("public")]
    public IActionResult Public() =&gt; Ok("Anyone can view this.");

    [Authorize] // authenticated users only
    [HttpGet("secure")]
    public IActionResult Secure()
        =&gt; Ok($"Hello {User.Identity?.Name}, you are authenticated.");

    [Authorize(Roles = "Admin")]
    [HttpGet("admin")]
    public IActionResult AdminOnly()
        =&gt; Ok("Admin access granted.");

    [Authorize(Roles = "Admin,Auditor")]
    [HttpGet("audit")]
    public IActionResult AdminOrAuditor()
        =&gt; Ok("Admin or Auditor access granted.");
}
</code></code></pre><h3>What happens internally when calling <code>/admin</code></h3><ul><li><p>User is authenticated</p></li><li><p>ASP.NET checks roles required: <code>"Admin"</code></p></li><li><p>It searches <code>HttpContext.User.Claims</code> for role claims</p></li><li><p>If match found &#8594; allow</p></li><li><p>If not &#8594; return 403 Forbidden</p></li></ul><div><hr></div><h2>8) &#8220;But my user is authenticated and still gets 403&#8221; (common reasons)</h2><h3>Reason A: Role claim type mismatch</h3><p>Token contains <code>"roles"</code> but your system expects <code>ClaimTypes.Role</code>.</p><p>Fix using <code>RoleClaimType</code>.</p><h3>Reason B: Roles are nested or formatted differently</h3><p>Token might store roles as a single string:</p><pre><code><code>"roles": "Admin"
</code></code></pre><p>Or as:</p><pre><code><code>"roles": "Admin,Auditor"
</code></code></pre><p>ASP.NET expects multiple claims or an array-like representation.<br>If not, you may need transformation.</p><h3>Reason C: Roles are not present at all</h3><p>Authentication succeeded, but roles weren&#8217;t included in the token.</p><p>This is common when:</p><ul><li><p>IdP config doesn&#8217;t emit roles</p></li><li><p>scopes are missing</p></li><li><p>claims mapping is not configured</p></li></ul><div><hr></div><h2>9) Role transformation (when you want to enrich roles post-auth)</h2><p>Sometimes you authenticate with a token, but roles are stored in your database.</p><p>In that case, after authentication succeeds, you can add roles via claims transformation.</p><h3>Example: Add roles after authentication</h3><pre><code><code>using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

public class RoleClaimsTransformation : IClaimsTransformation
{
    public Task&lt;ClaimsPrincipal&gt; TransformAsync(ClaimsPrincipal principal)
    {
        var identity = (ClaimsIdentity)principal.Identity!;

        // Example: add a role dynamically (replace with DB lookup)
        if (!identity.HasClaim(c =&gt; c.Type == ClaimTypes.Role &amp;&amp; c.Value == "Admin"))
        {
            identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
        }

        return Task.FromResult(principal);
    }
}
</code></code></pre><p>Register it:</p><pre><code><code>builder.Services.AddScoped&lt;IClaimsTransformation, RoleClaimsTransformation&gt;();
</code></code></pre><p>Now roles can be attached <strong>after authentication</strong> and then authorization will evaluate them.</p><p>This is a clean pattern when your IdP doesn&#8217;t provide roles.</p><div><hr></div><h2>10) The clean mental model for juniors</h2><p>After authentication:</p><ul><li><p><code>HttpContext.User</code> exists</p></li><li><p>It contains claims</p></li><li><p>Role checks are claim checks</p></li><li><p><code>[Authorize(Roles="X")]</code> is evaluated in <code>UseAuthorization()</code></p></li><li><p>401 means &#8220;no identity&#8221;</p></li><li><p>403 means &#8220;identity exists, but role mismatch&#8221;</p></li></ul><p>If you keep this picture, role-based authorization becomes predictable.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share DotNet Full Stack Dev&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share DotNet Full Stack Dev</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/p/role-based-authorization-in-aspnet/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/p/role-based-authorization-in-aspnet/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Inside SQL Server: What Actually Happens When You Execute a SQL Query]]></title><description><![CDATA[For content overview videos]]></description><link>https://dotnetfullstackdev.substack.com/p/inside-sql-server-what-actually-happens</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/inside-sql-server-what-actually-happens</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Tue, 16 Dec 2025 18:07:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!eqOh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>Every developer has written this line thousands of times:</p><pre><code><code>SELECT * FROM Orders WHERE OrderId = 101;
</code></code></pre><p>You press <strong>Execute</strong>.<br>A result appears.<br>And that&#8217;s it&#8230; right?</p><p>Not really.</p><p>Behind that single query, <strong>SQL Server runs an entire internal pipeline</strong> involving:</p><ul><li><p>parsing</p></li><li><p>optimization</p></li><li><p>compilation</p></li><li><p>memory management</p></li><li><p>locking</p></li><li><p>execution engines</p></li><li><p>storage engines</p></li><li><p>buffer pools</p></li><li><p>transaction logs</p></li></ul><p>This article takes you <strong>inside SQL Server</strong>, step by step, to understand what really happens <strong>from the moment you hit Execute until rows are returned</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eqOh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eqOh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 424w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 848w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 1272w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eqOh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png" width="1276" height="715" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:715,&quot;width&quot;:1276,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55611,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/181811729?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eqOh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 424w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 848w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 1272w, https://substackcdn.com/image/fetch/$s_!eqOh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78046d5e-3183-470a-ae2d-fe6cab195d5c_1276x715.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>The Journey Begins: Client Sends the Query</h2><p>When you execute a query from:</p><ul><li><p>SQL Server Management Studio (SSMS)</p></li><li><p>an application (C#, Java, Python)</p></li><li><p>a reporting tool</p></li></ul><p>The query does <strong>not</strong> directly hit the database files.</p><p>First, it travels over the network using the <strong>TDS protocol (Tabular Data Stream)</strong>.</p><h3>What SQL Server receives</h3><p>SQL Server receives:</p><ul><li><p>the SQL text</p></li><li><p>session information</p></li><li><p>login context</p></li><li><p>transaction settings</p></li><li><p>language and date formats</p></li></ul><p>At this point, SQL Server has <strong>only text</strong>, not an execution plan.</p><h2>Parsing: &#8220;Is This SQL Even Valid?&#8221;</h2><p>The first internal component that touches your query is the <strong>Parser</strong>.</p><h3>What parsing means</h3><p>Parsing answers basic questions:</p><ul><li><p>Is the SQL syntax correct?</p></li><li><p>Are keywords valid?</p></li><li><p>Are parentheses balanced?</p></li><li><p>Is the query structure meaningful?</p></li></ul><p>If you write:</p><pre><code><code>SELEC * FROM Orders
</code></code></pre><p>Parsing fails immediately.</p><h3>Important clarification</h3><p>Parsing does <strong>not</strong>:</p><ul><li><p>check if tables exist</p></li><li><p>check indexes</p></li><li><p>check performance</p></li></ul><p>It only validates <strong>grammar</strong>.</p><p>If parsing succeeds, SQL Server moves to the next stage.</p><h2>Binding (Algebrizer): &#8220;What Does This Query Refer To?&#8221;</h2><p>Now SQL Server needs to understand <strong>what your query is talking about</strong>.</p><p>This step is often called:</p><ul><li><p><strong>Binding</strong></p></li><li><p>or <strong>Algebrization</strong></p></li></ul><h3>What happens here</h3><p>SQL Server:</p><ul><li><p>Resolves table names</p></li><li><p>Resolves column names</p></li><li><p>Checks schemas</p></li><li><p>Validates data types</p></li><li><p>Verifies permissions</p></li><li><p>Expands <code>*</code> into actual columns</p></li></ul><p>For example:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><pre><code><code>SELECT * FROM Orders
</code></code></pre><p>Becomes:</p><pre><code><code>SELECT OrderId, OrderDate, CustomerId, TotalAmount FROM dbo.Orders
</code></code></pre><p>Now SQL Server knows <strong>exactly</strong> what objects are involved.</p><p>If a table or column doesn&#8217;t exist, this is where the error is thrown.</p><h2>Query Optimization: &#8220;What Is the Fastest Way to Get the Data?&#8221;</h2><p>This is the <strong>most intelligent and expensive part</strong> of query processing.</p><p>SQL Server now hands the query to the <strong>Query Optimizer</strong>.</p><h3>What the optimizer does</h3><p>The optimizer asks:</p><blockquote><p>&#8220;There are many ways to execute this query.<br>Which one is the cheapest?&#8221;</p></blockquote><p>Here, <em>cheap</em> means:</p><ul><li><p>less CPU</p></li><li><p>fewer reads</p></li><li><p>less memory</p></li><li><p>faster execution</p></li></ul><h3>How SQL Server thinks (important mental model)</h3><p>SQL Server does <strong>not</strong> run your query and then see how fast it is.</p><p>Instead, it:</p><ol><li><p>Imagines many execution plans</p></li><li><p>Estimates the cost of each plan</p></li><li><p>Chooses the lowest-cost plan</p></li></ol><p>This is based on:</p><ul><li><p>table statistics</p></li><li><p>index statistics</p></li><li><p>data distribution</p></li><li><p>available indexes</p></li><li><p>estimated row counts</p></li></ul><h3>Example</h3><p>For this query:</p><pre><code><code>SELECT * FROM Orders WHERE OrderId = 101;
</code></code></pre><p>Possible plans include:</p><ul><li><p>Full table scan</p></li><li><p>Clustered index seek</p></li><li><p>Non-clustered index seek + key lookup</p></li></ul><p>The optimizer evaluates all options and picks <strong>one</strong>.</p><h2>Plan Cache: &#8220;Have I Seen This Query Before?&#8221;</h2><p>Before generating a new plan, SQL Server checks the <strong>Plan Cache</strong>.</p><h3>Why this matters</h3><p>Generating an execution plan is expensive.</p><p>So SQL Server asks:</p><blockquote><p>&#8220;Do I already have a plan for this exact query?&#8221;</p></blockquote><p>If yes:</p><ul><li><p>It reuses the existing plan</p></li><li><p>Saves time and CPU</p></li></ul><p>If no:</p><ul><li><p>A new plan is compiled</p></li><li><p>Stored in the plan cache for future use</p></li></ul><p>This is why <strong>parameterization</strong> and <strong>query consistency</strong> matter so much for performance.</p><h2>Compilation: Turning Plan into Executable Instructions</h2><p>Once the optimizer selects the best plan, SQL Server <strong>compiles</strong> it.</p><p>Compilation produces:</p><ul><li><p>an execution plan</p></li><li><p>physical operators (Index Seek, Scan, Hash Join, Nested Loop)</p></li><li><p>memory grant estimates</p></li><li><p>execution order</p></li></ul><p>At this point:</p><ul><li><p>SQL text &#8594; logical plan &#8594; physical plan</p></li><li><p>SQL Server knows exactly <em>how</em> it will fetch the data</p></li></ul><p>Still, no data has been read yet.</p><h2>Execution Begins: The Execution Engine Takes Over</h2><p>Now the query moves from the <strong>Relational Engine</strong> to the <strong>Storage Engine</strong>.</p><h3>Important distinction</h3><ul><li><p><strong>Relational Engine</strong>: decides <em>what</em> to do</p></li><li><p><strong>Storage Engine</strong>: actually fetches data from disk/memory</p></li></ul><p>Execution now starts operator by operator.</p><h2>Buffer Pool: &#8220;Do I Already Have This Data in Memory?&#8221;</h2><p>Before touching disk, SQL Server checks the <strong>Buffer Pool</strong>.</p><h3>Buffer Pool explained simply</h3><p>The buffer pool is SQL Server&#8217;s in-memory cache of data pages.</p><p>When SQL Server needs a data page:</p><ol><li><p>Check buffer pool</p></li><li><p>If found &#8594; memory read (very fast)</p></li><li><p>If not found &#8594; disk read (slow)</p></li></ol><p>This is why:</p><ul><li><p>memory is critical for SQL Server</p></li><li><p>repeated queries get faster</p></li><li><p>cold cache vs warm cache matters</p></li></ul><h2>Storage Engine: Reading Data Pages</h2><p>If data is not in memory:</p><ul><li><p>SQL Server reads <strong>8KB pages</strong> from disk</p></li><li><p>Pages are loaded into the buffer pool</p></li><li><p>Data is returned to the execution engine</p></li></ul><p>SQL Server never reads individual rows.<br>It always works in <strong>pages</strong>.</p><h2>Locks &amp; Transactions: Keeping Data Safe</h2><p>While reading or modifying data, SQL Server must ensure consistency.</p><h3>What SQL Server does</h3><ul><li><p>Acquires locks (shared, exclusive, intent)</p></li><li><p>Honors transaction isolation level</p></li><li><p>Prevents dirty reads or conflicts (based on settings)</p></li></ul><p>Even a simple <code>SELECT</code> may acquire locks depending on:</p><ul><li><p>isolation level</p></li><li><p>indexes used</p></li><li><p>execution plan</p></li></ul><h2>Row Processing &amp; Operator Execution</h2><p>Now SQL Server executes the plan step by step.</p><p>For example:</p><ul><li><p>Index Seek &#8594; retrieves matching rows</p></li><li><p>Filter &#8594; applies WHERE clause</p></li><li><p>Join &#8594; combines rows from multiple tables</p></li><li><p>Sort &#8594; orders results</p></li><li><p>Aggregate &#8594; computes SUM, COUNT, etc.</p></li></ul><p>Each operator:</p><ul><li><p>pulls rows from the previous operator</p></li><li><p>processes them</p></li><li><p>passes results upward</p></li></ul><p>This happens <strong>row by row</strong>, not all at once.</p><h2>Returning Results to the Client</h2><p>As rows are produced:</p><ul><li><p>SQL Server streams them back to the client</p></li><li><p>It does not wait for the entire result set</p></li></ul><p>That&#8217;s why:</p><ul><li><p>you may start seeing rows early</p></li><li><p>large queries return results gradually</p></li></ul><p>The client displays the data as it arrives.</p><h2>Cleanup: Memory, Locks, and Execution Context</h2><p>Once execution finishes:</p><ul><li><p>locks are released</p></li><li><p>memory grants are freed</p></li><li><p>execution context is cleaned</p></li><li><p>plan remains in cache (if eligible)</p></li></ul><p>The query lifecycle is complete.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><h2>Final Mental Model (Very Important)</h2><p>When you execute a SQL query, SQL Server does <strong>far more than &#8220;run it&#8221;</strong>.</p><p>It:</p><ol><li><p>Parses the SQL</p></li><li><p>Understands objects and permissions</p></li><li><p>Optimizes execution strategy</p></li><li><p>Reuses or compiles plans</p></li><li><p>Checks memory before disk</p></li><li><p>Reads data pages</p></li><li><p>Manages locks and transactions</p></li><li><p>Executes operators</p></li><li><p>Streams results back</p></li></ol><p>Once you understand this internal pipeline:</p><ul><li><p>execution plans make sense</p></li><li><p>indexing decisions become logical</p></li><li><p>performance tuning becomes systematic</p></li><li><p>SQL Server feels predictable, not magical</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/p/inside-sql-server-what-actually-happens/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/p/inside-sql-server-what-actually-happens/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share DotNet Full Stack Dev&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share DotNet Full Stack Dev</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Three Ways Backends Talk: REST, gRPC, and SignalR Explained Like You’re New to Backend Development]]></title><description><![CDATA[When someone starts learning backend development, there&#8217;s a moment of confusion that almost everyone experiences.]]></description><link>https://dotnetfullstackdev.substack.com/p/three-ways-backends-talk-rest-grpc</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/three-ways-backends-talk-rest-grpc</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Tue, 16 Dec 2025 00:41:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XXY8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When someone starts learning backend development, there&#8217;s a moment of confusion that almost everyone experiences. You build some logic on the server, but then you ask yourself:</p><blockquote><p>&#8220;How does my frontend, mobile app, or another backend actually talk to this server?&#8221;</p></blockquote><p>Very soon, you hear words like <strong>REST</strong>, <strong>gRPC</strong>, and <strong>SignalR</strong> being used casually in meetings, tutorials, and job descriptions. People say things like:</p><ul><li><p>&#8220;Expose a REST API&#8221;</p></li><li><p>&#8220;Use gRPC for internal services&#8221;</p></li><li><p>&#8220;SignalR will handle real-time updates&#8221;</p></li></ul><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>For someone new, these sound like advanced, unrelated technologies.<br>But in reality, they are simply <strong>different ways of solving the same core problem</strong> &#8212; communication.</p><p>This article explains <strong>what these are</strong>, <strong>why they exist</strong>, <strong>why only these three dominate most backend systems</strong>, and <strong>whether other options exist</strong>, all in a slow, story-like manner.</p><h2>First, the core idea (before protocols)</h2><p>Before we talk about REST, gRPC, or SignalR, we must understand one foundational truth of backend systems.</p><p>A backend is rarely alone.</p><p>It almost always needs to communicate with something else:</p><ul><li><p>a web browser</p></li><li><p>a mobile application</p></li><li><p>another backend service</p></li><li><p>a dashboard</p></li><li><p>an external system</p></li></ul><p>These systems are:</p><ul><li><p>running on different machines</p></li><li><p>connected through networks</p></li><li><p>possibly written in different programming languages</p></li><li><p>possibly maintained by different teams</p></li></ul><p>Because of this separation, <strong>they cannot just call each other&#8217;s methods directly</strong> like normal code inside the same application.</p><p>They need a <strong>defined way to talk</strong>.</p><p>That defined way is what we call a <strong>communication protocol</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XXY8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XXY8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 424w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 848w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 1272w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XXY8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png" width="1049" height="432" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:432,&quot;width&quot;:1049,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81440,&quot;alt&quot;:&quot;https://www.youtube.com/@DotNetFullstackDev&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/181743567?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="https://www.youtube.com/@DotNetFullstackDev" title="https://www.youtube.com/@DotNetFullstackDev" srcset="https://substackcdn.com/image/fetch/$s_!XXY8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 424w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 848w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 1272w, https://substackcdn.com/image/fetch/$s_!XXY8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F232fd80a-9cc7-4e89-a7cd-5222d4bb7ba4_1049x432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://www.youtube.com/@DotNetFullstackDev">caption...</a></figcaption></figure></div><h2>What is a &#8220;protocol&#8221; in simple terms?</h2><p>A protocol is not a library or a framework.<br>It is simply an <strong>agreement</strong>.</p><p>It answers questions like:</p><ul><li><p>How does a message start?</p></li><li><p>How does it end?</p></li><li><p>What format is used?</p></li><li><p>Who speaks first?</p></li><li><p>How does the receiver know what the sender wants?</p></li></ul><h3>Real-world analogy</h3><p>Think about making a phone call:</p><ul><li><p>You say &#8220;Hello&#8221; first</p></li><li><p>You wait for a response</p></li><li><p>You take turns speaking</p></li><li><p>You say &#8220;Bye&#8221; before hanging up</p></li></ul><p>If one person suddenly starts shouting random words, communication breaks.</p><p>Backend communication works the same way.<br>Both sides must follow the same protocol rules.</p><h2>REST &#8212; The Most Popular and Simplest One</h2><h3>REST is like sending letters by post</h3><p>REST is the most widely used backend communication style, mainly because it is <strong>simple, familiar, and built on top of HTTP</strong>, which the entire internet already understands.</p><p>The REST model follows a very straightforward pattern:</p><ul><li><p>Client sends a request</p></li><li><p>Server processes it</p></li><li><p>Server sends a response</p></li><li><p>Connection ends</p></li></ul><p>Each interaction is <strong>self-contained</strong>.</p><h3>How REST works (no jargon)</h3><p>When a frontend calls a REST API:</p><ol><li><p>It sends an HTTP request (GET, POST, PUT, DELETE)</p></li><li><p>The request contains a URL and optional data</p></li><li><p>The backend reads the request</p></li><li><p>The backend performs some logic</p></li><li><p>The backend sends back a response</p></li><li><p>The interaction ends</p></li></ol><p>There is no memory of previous requests unless you explicitly build it.</p><h3>Why REST became so popular</h3><p>REST didn&#8217;t invent new technology.<br>It simply <strong>used what already existed</strong>:</p><ul><li><p>HTTP</p></li><li><p>URLs</p></li><li><p>browsers</p></li><li><p>text-based formats like JSON</p></li></ul><p>Because of this:</p><ul><li><p>developers could test APIs in a browser</p></li><li><p>debugging was easy</p></li><li><p>logs were readable</p></li><li><p>tools like Postman worked instantly</p></li></ul><p>This accessibility made REST explode in popularity.</p><h3>When REST is perfect</h3><p>REST shines when:</p><ul><li><p>you are building CRUD-based applications</p></li><li><p>you expose public APIs</p></li><li><p>readability and simplicity matter</p></li><li><p>clients vary (web, mobile, third-party)</p></li></ul><p>It is often the <strong>first protocol developers learn</strong>, and for good reason.</p><h3>REST limitations</h3><p>REST is not ideal for everything.</p><p>Because it is request&#8211;response:</p><ul><li><p>the client must keep asking for updates</p></li><li><p>the server cannot push data by itself</p></li><li><p>frequent polling increases load</p></li><li><p>JSON payloads can become large</p></li></ul><p>As systems became more complex and performance-sensitive, developers started looking for alternatives.</p><h2>gRPC &#8212; When Performance and Efficiency Matter</h2><h3>gRPC is like a high-speed bullet train</h3><p>gRPC was designed to solve problems REST struggles with, especially in <strong>backend-to-backend communication</strong>.</p><p>Instead of sending readable text like JSON, gRPC uses <strong>binary data</strong>, which is:</p><ul><li><p>smaller</p></li><li><p>faster to parse</p></li><li><p>more efficient over the network</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><h3>How gRPC works (simple view)</h3><p>In gRPC:</p><ul><li><p>communication still happens over HTTP</p></li><li><p>but it uses HTTP/2 instead of HTTP/1.1</p></li><li><p>data is defined using strict contracts (.proto files)</p></li><li><p>both client and server generate code from those contracts</p></li></ul><p>This removes ambiguity and reduces errors.</p><h3>Real-world analogy</h3><p>REST is like writing letters that anyone can read.<br>gRPC is like sending compressed files between machines.</p><p>Humans prefer letters.<br>Machines prefer compressed files.</p><h3>Why companies use gRPC</h3><p>Companies choose gRPC when:</p><ul><li><p>performance is critical</p></li><li><p>many services talk to each other</p></li><li><p>data models must be consistent</p></li><li><p>network efficiency matters</p></li></ul><p>It is extremely common in:</p><ul><li><p>microservices</p></li><li><p>internal service communication</p></li><li><p>cloud-native systems</p></li></ul><h3>When gRPC is ideal</h3><p>gRPC works best when:</p><ul><li><p>clients are known and controlled</p></li><li><p>both sides are backend services</p></li><li><p>low latency is important</p></li><li><p>high throughput is required</p></li></ul><h3>Why gRPC is not everywhere</h3><p>Despite its power, gRPC has drawbacks:</p><ul><li><p>harder to debug manually</p></li><li><p>not human-readable</p></li><li><p>not naturally supported by browsers</p></li><li><p>learning curve is steeper</p></li></ul><p>This is why REST remains dominant for public-facing APIs.</p><h2>SignalR &#8212; When Backend Talks <em>First</em></h2><h3>SignalR is like a live phone call</h3><p>REST and gRPC share one limitation:</p><blockquote><p>the client must initiate every request</p></blockquote><p>But many modern applications need:</p><ul><li><p>instant notifications</p></li><li><p>live updates</p></li><li><p>continuous data streams</p></li></ul><p>SignalR exists to solve this exact problem.</p><h3>How SignalR works (simple)</h3><p>With SignalR:</p><ul><li><p>the client opens a long-lived connection</p></li><li><p>the connection stays open</p></li><li><p>the server can send data anytime</p></li><li><p>the client doesn&#8217;t need to ask repeatedly</p></li></ul><p>This enables <strong>real-time communication</strong>.</p><h3>Real-world analogy</h3><p>REST is like checking your phone again and again for messages.<br>SignalR is like receiving a notification immediately when something happens.</p><h3>Why SignalR exists</h3><p>SignalR avoids:</p><ul><li><p>unnecessary polling</p></li><li><p>wasted network traffic</p></li><li><p>delayed updates</p></li></ul><p>It creates a smoother user experience.</p><h3>When SignalR is perfect</h3><p>SignalR is ideal for:</p><ul><li><p>chat applications</p></li><li><p>live dashboards</p></li><li><p>notifications</p></li><li><p>collaborative apps</p></li><li><p>real-time monitoring systems</p></li></ul><h3>SignalR limitations</h3><p>SignalR keeps connections open:</p><ul><li><p>which consumes server resources</p></li><li><p>requires careful scaling</p></li><li><p>is unnecessary for simple APIs</p></li></ul><p>That&#8217;s why it complements REST and gRPC instead of replacing them.</p><h2>Do other backend communication options exist?</h2><p>Yes &#8212; but they are usually <strong>specialized tools</strong>, not general-purpose solutions.</p><p>GraphQL focuses on flexible queries.<br>WebSockets provide raw real-time communication.<br>Message queues enable asynchronous event processing.<br>SOAP exists mainly for legacy systems.</p><p>Each solves a specific problem, but none replace all three major protocols.</p><h2>Why REST, gRPC, and SignalR dominate</h2><p>These three dominate because <strong>together they cover almost every communication scenario</strong>:</p><ul><li><p>REST handles simple request&#8211;response APIs</p></li><li><p>gRPC handles fast internal communication</p></li><li><p>SignalR handles real-time updates</p></li></ul><p>They are not competitors &#8212; they are <strong>complementary</strong>.</p><h2>A simple mental map (very important)</h2><p>Choosing a protocol becomes easy when you ask the right question:</p><ul><li><p>&#8220;Do I need simple request&#8211;response?&#8221; &#8594; REST</p></li><li><p>&#8220;Do I need fast service-to-service calls?&#8221; &#8594; gRPC</p></li><li><p>&#8220;Do I need real-time server push?&#8221; &#8594; SignalR</p></li></ul><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><h2>Final takeaway (for beginners)</h2><p>Backend communication is not about picking the &#8220;best&#8221; protocol.</p><p>It&#8217;s about understanding <strong>what kind of conversation your systems need to have</strong>.</p><p>REST, gRPC, and SignalR exist because:</p><ul><li><p>backend systems evolved</p></li><li><p>performance needs changed</p></li><li><p>user expectations grew</p></li></ul><p>Once you understand <strong>why they exist</strong>, choosing between them becomes natural.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share DotNet Full Stack Dev&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share DotNet Full Stack Dev</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/p/three-ways-backends-talk-rest-grpc/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/p/three-ways-backends-talk-rest-grpc/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Inside the .NET Garbage Collector — A Step-by-Step Journey Through Generational GC ]]></title><description><![CDATA[Explained from scratch to deeper]]></description><link>https://dotnetfullstackdev.substack.com/p/inside-the-net-garbage-collector</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/inside-the-net-garbage-collector</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sun, 07 Dec 2025 09:33:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!LIin!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>Modern .NET uses one of the world&#8217;s most advanced memory-management engines &#8212; the <strong>Generational Garbage Collector</strong>.<br>But despite how powerful it is, many developers still feel unsure about:</p><ul><li><p>What exactly happens inside the GC?</p></li><li><p>Why does .NET divide memory into Generations?</p></li><li><p>How does the GC decide <em>when</em> to run?</p></li><li><p>What does &#8220;promotion to Gen1/Gen2&#8221; really mean?</p></li><li><p>When does Full GC happen?</p></li></ul><p>The best way to understand this is to visualize the GC as a <strong>flow</strong>, not a single event.</p><p>Below, we walk through <strong>each step</strong> of the Generational GC process.</p><div><hr></div><h1>&#129513; <strong>Step 1 &#8212; Object Allocated in Gen0</strong></h1><h3><em>&#8220;Every new object starts its life in the nursery.&#8221;</em></h3><p>When your application executes:</p><pre><code><code>var customer = new Customer();
</code></code></pre><p>.NET allocates memory for this new object in the <strong>Gen0 heap</strong>, which is designed for:</p><ul><li><p>fast memory allocation</p></li><li><p>short-lived objects</p></li><li><p>minimal overhead</p></li></ul><p>Think of Gen0 as a <strong>nursery</strong> &#8212; a space created for newborn objects.</p><p>Most objects in typical applications die young. For example:</p><ul><li><p>temporary strings</p></li><li><p>LINQ intermediate objects</p></li><li><p>short-lived DTOs</p></li><li><p>loop variables</p></li><li><p>objects used inside methods only once</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LIin!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LIin!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!LIin!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!LIin!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!LIin!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LIin!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png" width="1024" height="1536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:669469,&quot;alt&quot;:&quot;https://www.youtube.com/@DotNetFullstackDev&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/180942928?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="https://www.youtube.com/@DotNetFullstackDev" title="https://www.youtube.com/@DotNetFullstackDev" srcset="https://substackcdn.com/image/fetch/$s_!LIin!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!LIin!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!LIin!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!LIin!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff218ad42-4237-48af-8c7e-139f4289c2b7_1024x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Because these objects are created and destroyed quickly, Gen0 is optimized to handle them efficiently.</p><div><hr></div><h1>&#129513; <strong>Step 2 &#8212; Gen0 Threshold Exceeded</strong></h1><h3><em>&#8220;The nursery fills up.&#8221;</em></h3><p>Gen0 has a small memory size because:</p><ul><li><p>small memory = fast cleanup</p></li><li><p>frequent collection = efficient space recovery</p></li></ul><p>As your program runs, more and more objects are created:</p><pre><code><code>new X()
new Y()
new Z()
</code></code></pre><p>Once Gen0 becomes full, .NET must take action.</p><p>This is when the runtime says:</p><blockquote><p>&#8220;Gen0 is full &#8212; time to clean up!&#8221;</p></blockquote><p>This triggers the next step.</p><div><hr></div><h1>&#129513; <strong>Step 3 &#8212; Gen0 Collection</strong></h1><h3><em>&#8220;The GC quickly removes objects that are no longer needed.&#8221;</em></h3><p>A <strong>Gen0 GC</strong> is the fastest type of garbage collection.</p><p>How does GC know what to delete?</p><ul><li><p>It pauses the application <em>very briefly</em></p></li><li><p>Checks which objects are still reachable (alive)</p></li><li><p>Removes the ones that are no longer needed</p></li></ul><p>All objects that survive this step get <strong>promoted</strong> to Gen1.</p><p>Why?<br>Because if an object survived a cleanup, it may be longer-lived.</p><div><hr></div><h1>&#129513; <strong>Step 4 &#8212; Surviving Objects Promoted to Gen1</strong></h1><h3><em>&#8220;If you survived the nursery cleanup, you move to the next room.&#8221;</em></h3><p>Objects that weren&#8217;t cleaned up in Gen0 are considered more likely to live longer.</p><p>So .NET moves them to <strong>Gen1</strong>, which is a middle ground:</p><ul><li><p>bigger space</p></li><li><p>collects less often</p></li><li><p>handles objects with moderate lifetimes</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>Typical Gen1 survivors include:</p><ul><li><p>objects reused across multiple method calls</p></li><li><p>service instances</p></li><li><p>collections that grow during processing</p></li><li><p>cached objects</p></li></ul><p>Think of Gen1 as a <strong>teenager stage</strong> &#8212; not newborn, not adult yet.</p><div><hr></div><h1>&#129513; <strong>Step 5 &#8212; Gen1 Collection</strong></h1><h3><em>&#8220;If Gen1 starts filling up, GC cleans it too.&#8221;</em></h3><p>Eventually, Gen1 also fills.</p><p>When that happens, .NET triggers a <strong>Gen1 collection</strong>, which:</p><ul><li><p>cleans up dead Gen1 objects</p></li><li><p>promotes surviving objects to Gen2</p></li></ul><p>A Gen1 collection is slightly slower than Gen0, but still efficient.</p><p>This step only happens when needed &#8212; i.e., when Gen1 runs low on memory.</p><div><hr></div><h1>&#129513; <strong>Step 6 &#8212; Long-Lived Objects Promoted to Gen2</strong></h1><h3><em>&#8220;Any object that survives long enough becomes a long-term resident.&#8221;</em></h3><p>Objects that survive Gen0 and Gen1 collections graduate to <strong>Gen2</strong>, the adult generation.</p><p>Gen2 contains:</p><ul><li><p>application-wide services</p></li><li><p>large internal object graphs</p></li><li><p>caches</p></li><li><p>static data</p></li><li><p>objects that stay alive for the lifetime of the app</p></li></ul><p>Gen2 collection is rare and expensive, so .NET avoids touching it frequently.</p><p>Think of Gen2 as <strong>long-term storage</strong>.</p><div><hr></div><h1>&#129513; <strong>Step 7 &#8212; Full GC (Triggered Only When Needed)</strong></h1><h3><em>&#8220;When memory pressure becomes high, GC cleans everything.&#8221;</em></h3><p>A <strong>Full GC</strong> is the most expensive type of garbage collection.<br>It includes:</p><ul><li><p>Gen0 cleanup</p></li><li><p>Gen1 cleanup</p></li><li><p>Gen2 cleanup</p></li><li><p>LOH (Large Object Heap) cleanup</p></li></ul><p>Full GC happens only under conditions like:</p><ul><li><p>very high memory usage</p></li><li><p>large object allocations</p></li><li><p>low available physical memory</p></li><li><p>explicit calls like <code>GC.Collect()</code> (not recommended)</p></li></ul><p>Full GC introduces a longer pause because the GC checks the entire managed heap.</p><p>.NET tries hard to avoid this, which is why generations exist &#8212; to optimize GC and minimize full collections.</p><div><hr></div><h1>&#129513; <strong>Step 8 &#8212; Compaction &amp; Finalization</strong></h1><h3><em>&#8220;GC organizes memory and finalizes special objects.&#8221;</em></h3><p>After deleting unused objects, .NET may:</p><h3><strong>1. Compact memory</strong></h3><p>This reduces fragmentation by:</p><ul><li><p>shifting objects together</p></li><li><p>freeing large continuous memory blocks</p></li></ul><p>Compaction is vital because memory fragmentation can crash your app or slow down JIT allocations.</p><h3><strong>2. Run finalizers (if any)</strong></h3><p>Objects with destructors (<code>~MyClass()</code>) are queued for special cleanup.</p><p>Finalizers delay collection, so avoid them unless necessary.</p><div><hr></div><h1>&#129513; <strong>Step 9 &#8212; Execution Resumes</strong></h1><h3><em>&#8220;The app continues running normally.&#8221;</em></h3><p>Once GC completes its work:</p><ul><li><p>memory is clean</p></li><li><p>generations are reorganized</p></li><li><p>objects are promoted or removed</p></li><li><p>memory pressure reduced</p></li></ul><p>The application resumes exactly where it paused.</p><p>Modern .NET uses <strong>Background GC</strong>, meaning much of the GC work happens concurrently without stopping your program.</p><p>This gives you:</p><ul><li><p>faster apps</p></li><li><p>smoother performance</p></li><li><p>less noticeable GC pauses</p></li></ul><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1>&#11088; Putting It All Together</h1><p>The Generational GC system follows a beautiful, logical flow:</p><ol><li><p><strong>New objects go to Gen0</strong></p></li><li><p><strong>Gen0 fills &#8594; Collection happens</strong></p></li><li><p><strong>Survivors move to Gen1</strong></p></li><li><p><strong>Gen1 fills &#8594; Collection happens</strong></p></li><li><p><strong>Survivors move to Gen2</strong></p></li><li><p><strong>Gen2 grows slowly &#8594; Full GC if needed</strong></p></li><li><p><strong>Memory compacted &amp; finalized</strong></p></li><li><p><strong>Execution resumes with fresh memory</strong></p></li></ol><p>This is not just cleanup &#8212; it is a <strong>performance strategy</strong>:</p><ul><li><p>Fast cleaning of short-lived objects</p></li><li><p>Rare cleaning of long-lived objects</p></li><li><p>Reduced pauses</p></li><li><p>Smarter memory use</p></li><li><p>Predictable performance</p></li></ul><p>This is why .NET&#8217;s GC is one of the best in the industry.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/p/inside-the-net-garbage-collector/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/p/inside-the-net-garbage-collector/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share DotNet Full Stack Dev&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share DotNet Full Stack Dev</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Kerberos Meets Scala: A Developer’s Guide to Secure, Ticket-Based Authentication in the JVM World]]></title><description><![CDATA[From mythical guard dog to modern enterprise authentication &#8212; explained with real code.]]></description><link>https://dotnetfullstackdev.substack.com/p/kerberos-meets-scala-a-developers</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/kerberos-meets-scala-a-developers</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sat, 06 Dec 2025 09:37:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4tV6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>Kerberos is everywhere in large enterprises&#8212;finance, telecom, healthcare, pharma, analytics, and government networks.<br>If you&#8217;re building <strong>Scala-based microservices</strong> using:</p><ul><li><p>Akka HTTP</p></li><li><p>Play Framework</p></li><li><p>Apache Spark</p></li><li><p>Kafka clients</p></li><li><p>Hadoop/YARN</p></li><li><p>Distributed clusters</p></li></ul><p>&#8230;you will almost certainly encounter Kerberos.</p><h3>But here&#8217;s the real problem:</h3><p>Most Kerberos explanations feel like reading an ancient spellbook.<br>Terms like <em>TGT</em>, <em>SPN</em>, <em>KDC</em>, <em>AS-REQ</em>, <em>ticket enciphered</em> &#8212; overwhelm new developers immediately.</p><p>So let&#8217;s rewrite this story in a way that makes sense to a real programmer.</p><p>This blog walks you through:</p><ol><li><p>What Kerberos is (in simple human language)</p></li><li><p>How Kerberos works in a real enterprise</p></li><li><p>How Scala apps authenticate using Kerberos</p></li><li><p>Actual Scala code using JAAS + Kerberos principals</p></li><li><p>How Akka HTTP or Spark adopts Kerberos automatically</p></li></ol><p>By the end, you&#8217;ll feel confident building <strong>Kerberized Scala services</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4tV6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4tV6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4tV6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/180870034?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4tV6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!4tV6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F378cb4fd-0810-4353-ab80-1f5679dc4fef_1920x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h1><strong>1. Kerberos in One Simple Analogy (No Security Jargon Needed)</strong></h1><p>Imagine a big corporate building.</p><p>You don&#8217;t type your password at every door.<br>Instead:</p><ul><li><p>Security desk verifies you once using your ID card</p></li><li><p>They give you <strong>access stickers</strong> (tickets)</p></li><li><p>Each sticker lets you enter certain rooms</p></li><li><p>Rooms trust the sticker because security issued it</p></li></ul><p>This is exactly how Kerberos works.</p><ul><li><p><strong>Security desk = Domain Controller (KDC)</strong></p></li><li><p><strong>Your corporate login = Kerberos principal</strong></p></li><li><p><strong>Access stickers = Kerberos tickets</strong></p></li><li><p><strong>Rooms = Scala services like Spark, Akka, Kafka</strong></p></li></ul><p>Your Scala application is a &#8220;room&#8221; that only accepts visitors with valid stickers.</p><div><hr></div><h1><strong>2. How Kerberos Works &#8212; The Scala Developer&#8217;s Version</strong></h1><p>Kerberos uses two kinds of tickets:</p><h3><strong>TGT (Ticket Granting Ticket)</strong></h3><p>Issued when you authenticate the first time.</p><h3><strong>Service Ticket</strong></h3><p>Issued for each specific service (e.g., Kafka, Spark).</p><h3>High-level flow:</h3><ol><li><p><strong>User logs into the system &#8594; gets TGT</strong></p></li><li><p>Scala application requests access to a service (e.g., Kafka broker)</p></li><li><p>TGT &#8594; exchanged for <strong>Service Ticket</strong></p></li><li><p>Scala client sends the service ticket in every request</p></li><li><p>Service (broker/web API) validates ticket against KDC keys</p></li></ol><p>No passwords go over the network.</p><div><hr></div><h1><strong>3. Why Kerberos Matters in the Scala World</strong></h1><p>Scala is heavily used in:</p><ul><li><p>Big-data platforms</p></li><li><p>Event streaming systems</p></li><li><p>Distributed computing clusters</p></li><li><p>High-security enterprise environments</p></li></ul><p>These environments often run:</p><ul><li><p><strong>Hadoop / HDFS</strong> (Kerberos mandatory)</p></li><li><p><strong>Spark on YARN</strong></p></li><li><p><strong>Kafka clusters with SASL/Kerberos</strong></p></li><li><p><strong>Akka microservices inside corporate domains</strong></p></li></ul><p>Kerberos is the first-class citizen of security in JVM ecosystems.</p><div><hr></div><h1><strong>4. JAAS &#8212; The Bridge Between Scala and Kerberos</strong></h1><p>Scala uses the <strong>Java Authentication and Authorization Service (JAAS)</strong> under the hood.</p><p>Kerberos in Scala usually requires a JAAS config file:</p><h3><code>jaas.conf</code></h3><pre><code><code>Client {
    com.sun.security.auth.module.Krb5LoginModule required
    useKeyTab=true
    storeKey=true
    useTicketCache=false
    keyTab=&#8221;/opt/keys/scala-service.keytab&#8221;
    principal=&#8221;scala-service@YOURDOMAIN.COM&#8221;;
};
</code></code></pre><p>This file tells the JVM:</p><ul><li><p>Which principal to use</p></li><li><p>Where the keytab file is</p></li><li><p>Whether to use ticket cache</p></li></ul><div><hr></div><h1><strong>5. Authenticating with Kerberos in Pure Scala</strong></h1><h3>Scala code to login using JAAS + Kerberos:</h3><pre><code><code>import javax.security.auth.login.{AppConfigurationEntry, Configuration, LoginContext}

object KerberosAuthDemo {

  def main(args: Array[String]): Unit = {

    // Load the JAAS configuration
    System.setProperty(&#8221;java.security.auth.login.config&#8221;, &#8220;/opt/jaas.conf&#8221;)
    System.setProperty(&#8221;java.security.krb5.conf&#8221;, &#8220;/etc/krb5.conf&#8221;)

    try {
      val loginContext = new LoginContext(&#8221;Client&#8221;)
      loginContext.login()

      println(&#8221;&#10004; Kerberos authentication successful!&#8221;)
      println(&#8221;Authenticated principal: &#8220; +
        loginContext.getSubject.getPrincipals)
    }
    catch {
      case ex: Exception =&gt;
        println(&#8221;&#10060; Kerberos authentication failed: &#8220; + ex.getMessage)
    }
  }
}
</code></code></pre><p>What this code does:</p><ul><li><p>Reads JAAS file</p></li><li><p>Fetches the principal/keytab</p></li><li><p>Asks Kerberos KDC for a TGT</p></li><li><p>Logs the principal</p></li></ul><p>If authentication works, your Scala process now has a valid ticket.</p><div><hr></div><h1><strong>6. Kerberos + Akka HTTP (Server-Side Authentication)</strong></h1><p>Akka HTTP can sit behind:</p><ul><li><p>Apache/Nginx with Kerberos enabled</p></li><li><p>Or Windows AD domain reverse proxy</p></li></ul><p>Kerberos usually terminates at the reverse proxy, which then forwards identity headers to Akka.</p><p>Example Akka route:</p><pre><code><code>val route =
  extractRequest { req =&gt;
    val user = req.headers.find(_.name == &#8220;X-Authenticated-User&#8221;)
      .map(_.value)
      .getOrElse(&#8221;anonymous&#8221;)

    complete(s&#8221;Hello $user! You were authenticated by Kerberos.&#8221;)
  }
</code></code></pre><p>Your reverse proxy sets:</p><pre><code><code>X-Authenticated-User: DOMAIN\username
</code></code></pre><div><hr></div><h1><strong>7. Kerberos + Kafka (Very Common in Scala)</strong></h1><p>If your Scala service connects to Kafka:</p><pre><code><code>val props = new java.util.Properties()

props.put(&#8221;security.protocol&#8221;, &#8220;SASL_PLAINTEXT&#8221;)
props.put(&#8221;sasl.mechanism&#8221;, &#8220;GSSAPI&#8221;)
props.put(&#8221;sasl.kerberos.service.name&#8221;, &#8220;kafka&#8221;)
props.put(&#8221;bootstrap.servers&#8221;, &#8220;kafka1:9092&#8221;)
props.put(&#8221;group.id&#8221;, &#8220;my-scala-group&#8221;)

val consumer = new KafkaConsumer[String, String](props)
</code></code></pre><p>Kafka automatically uses JAAS + keytab to authenticate.</p><div><hr></div><h1><strong>8. Kerberos + Apache Spark (Another Huge Use Case)</strong></h1><p>In secured Hadoop clusters:</p><pre><code><code>kinit -kt spark.keytab spark-user@REALM.COM
spark-submit --principal spark-user --keytab spark.keytab job.jar
</code></code></pre><p>Spark obtains a TGT &#8594; then uses that to interact with:</p><ul><li><p>YARN</p></li><li><p>HDFS</p></li><li><p>Hive</p></li><li><p>Kafka</p></li></ul><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1><strong>9. Summary &#8212; Kerberos Isn&#8217;t Hard. It Was Just Never Explained Simply.</strong></h1><p>Kerberos feels complex because it mixes:</p><ul><li><p>network authentication</p></li><li><p>cryptography</p></li><li><p>AD configuration</p></li><li><p>JVM internals</p></li></ul><p>But at its heart, it&#8217;s simple:</p><blockquote><p><em>&#8220;Authenticate once &#8594; receive tickets &#8594; present tickets to Scala services.&#8221;</em></p></blockquote><p>Scala integrates beautifully with Kerberos because:</p><ul><li><p>JVM has native support</p></li><li><p>JAAS provides a simple configuration model</p></li><li><p>Frameworks like Spark, Kafka, and Akka already support it</p></li></ul>]]></content:encoded></item><item><title><![CDATA[CSV vs JSON vs Excel — How to Choose the Right Format & What to Consider When Preparing Each File (A Practical Guide for Developers & Analysts)]]></title><description><![CDATA[https://www.youtube.com/@DotNetFullstackDev]]></description><link>https://dotnetfullstackdev.substack.com/p/csv-vs-json-vs-excel-how-to-choose</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/csv-vs-json-vs-excel-how-to-choose</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Thu, 04 Dec 2025 03:46:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qfLi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></p><p>Today every project &#8212; whether it&#8217;s a .NET system, a BI pipeline, an ETL workflow, or a simple reporting job &#8212; eventually asks the same question:</p><blockquote><p><strong>&#8220;Should I output this data as CSV, JSON, or Excel?&#8221;</strong></p></blockquote><p>At first, it feels simple.<br>But when you start preparing the actual files, suddenly:</p><ul><li><p>Your CSV breaks because someone typed a comma inside a comment</p></li><li><p>Your JSON fails because of a missing bracket</p></li><li><p>Your Excel file becomes 45MB and refuses to open on someone&#8217;s laptop</p></li><li><p>Your API consumer says &#8220;we need nested objects, not flat data&#8221;</p></li><li><p>Your business stakeholder says &#8220;add colors and formulas inside Excel&#8221;</p></li></ul><p>This blog will remove all that confusion.</p><p>Let&#8217;s break down the <strong>key differentiating factors</strong>, <strong>real-time use cases</strong>, and <strong>best practices</strong> for preparing CSV, JSON, and Excel &#8212; the three most used data formats in the world.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qfLi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qfLi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qfLi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1327320,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/180672056?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qfLi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!qfLi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14716252-ad34-48fb-b2d4-b7df7168db93_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h1><strong>First Step: Understand What These File Formats Actually Represent</strong></h1><p>Before comparing, understand the nature of the formats.</p><h2><strong>CSV = Flat data</strong></h2><ul><li><p>Rows + columns</p></li><li><p>No hierarchy</p></li><li><p>Good for large datasets</p></li><li><p>Very lightweight</p></li><li><p>Supported everywhere</p></li></ul><h2><strong>JSON = Structured + hierarchical data</strong></h2><ul><li><p>Perfect for APIs</p></li><li><p>Supports nested objects, arrays</p></li><li><p>Human + machine friendly</p></li><li><p>Ideal for configuration or complex data structures</p></li></ul><h2><strong>Excel = Rich data container</strong></h2><ul><li><p>Tables</p></li><li><p>Formatting</p></li><li><p>Charts</p></li><li><p>Formulas</p></li><li><p>Multiple sheets</p></li><li><p>Business-friendly</p></li></ul><p>Think of it like this:</p><p>FormatReal-world analogyCSVA notebook with only lines &#8212; pure raw dataJSONA container with sections, boxes, and labelsExcelA decorated project file with charts, colors, and formulas</p><p>Now let&#8217;s go deeper.</p><div><hr></div><h1><strong>When Preparing CSV Files &#8212; Key Things to Consider</strong></h1><p>CSV is deceptively simple.</p><p>But preparing a CSV correctly requires precision, otherwise one wrong comma breaks the entire dataset.</p><h2><strong>1. Always define your delimiter (comma, semicolon, tab)</strong></h2><p>Default is comma (<code>,</code>).<br>But sometimes fields <strong>themselves contain commas</strong>:</p><pre><code><code>&#8220;John, Alex&#8221;, 29, &#8220;India&#8221;
</code></code></pre><p>If you do not quote such fields, your CSV becomes invalid.</p><p>Many European countries prefer semicolon (<code>;</code>) because comma is used as a decimal separator.</p><h2><strong>2. Escape or quote values that contain special characters</strong></h2><p>Special characters include:</p><ul><li><p>commas</p></li><li><p>newlines</p></li><li><p>tabs</p></li><li><p>quotes</p></li></ul><p>Follow correct quoting:</p><pre><code><code>&#8220;Hello, World&#8221;
&#8220;This is a &#8220;&#8221;quoted&#8221;&#8220; value&#8221;
&#8220;Line1
Line2&#8221;
</code></code></pre><h2><strong>3. Keep column order consistent</strong></h2><p>Never reorder columns between exports unless absolutely necessary.</p><p>Systems downstream may break.</p><h2><strong>4. Avoid merging data types in the same column</strong></h2><p>Bad:</p><pre><code><code>Name, Value
Age, 30
Price, $15
</code></code></pre><p>Good:</p><pre><code><code>FieldName, FieldValue, FieldType
Age, 30, Integer
Price, 15, Currency
</code></code></pre><h2><strong>5. Avoid leading zeros if values represent IDs</strong></h2><p>Excel automatically removes leading zeros (e.g., <code>00123 &#8594; 123</code>).</p><p>Workaround:</p><ul><li><p>Quote them <code>&#8220;00123&#8221;</code></p></li><li><p>Prepend <code>&#8216;</code> (apostrophe)</p></li></ul><h2><strong>6. Choose UTF-8 encoding</strong></h2><p>Prevent issues with:</p><ul><li><p>special characters</p></li><li><p>Indian regional languages</p></li><li><p>emojis</p></li><li><p>accented names</p></li></ul><h2><strong>7. Don&#8217;t include formulas</strong></h2><p>CSV is flat text.<br>No formulas, no formatting.<br>If you need formulas &#8594; Excel.</p><div><hr></div><h1><strong>When Preparing JSON Files &#8212; Key Things to Consider</strong></h1><p>JSON supports complex, deeply nested structures.</p><p>But that power brings its own challenges.</p><h2><strong>1. Valid JSON structure is mandatory</strong></h2><p>One missing <code>}</code> will break everything.</p><p>Use online validators or automated serializers.</p><h2><strong>2. Be careful with data types</strong></h2><p>JSON allows:</p><ul><li><p>numbers</p></li><li><p>strings</p></li><li><p>arrays</p></li><li><p>objects</p></li><li><p>booleans</p></li><li><p>null</p></li></ul><p>All numbers default to float / double type &#8212; watch out for precision (especially with money).</p><h2><strong>3. Decide between compact vs pretty JSON</strong></h2><p>Compact:</p><pre><code><code>{&#8221;user&#8221;:&#8221;John&#8221;,&#8221;age&#8221;:29}
</code></code></pre><p>Pretty formatted:</p><pre><code><code>{
  &#8220;user&#8221;: &#8220;John&#8221;,
  &#8220;age&#8221;: 29
}
</code></code></pre><p>Pretty printing is good for humans;<br>compact is good for APIs and file transfer.</p><h2>&#10004; <strong>4. Use consistent naming style</strong></h2><ul><li><p>camelCase</p></li><li><p>PascalCase</p></li><li><p>snake_case</p></li></ul><p>Example:</p><pre><code><code>{
  &#8220;firstName&#8221;: &#8220;John&#8221;,
  &#8220;lastName&#8221;: &#8220;Alex&#8221;
}
</code></code></pre><h2><strong>5. Keep boolean values boolean, not strings</strong></h2><p>Bad:</p><pre><code><code>&#8220;isActive&#8221;: &#8220;true&#8221;
</code></code></pre><p>Good:</p><pre><code><code>&#8220;isActive&#8221;: true
</code></code></pre><h2><strong>6. Large JSON &#8594; consider splitting</strong></h2><p>If your JSON is larger than 5MB:</p><ul><li><p>Break into smaller JSON lines &#8594; NDJSON (&#8220;newline delimited JSON&#8221;)</p></li><li><p>Use streaming serializers</p></li></ul><h2><strong>7. JSON doesn&#8217;t support comments</strong></h2><p>Never write:</p><pre><code><code>{
   // this is a comment
}
</code></code></pre><p>It breaks parsing.</p><div><hr></div><h1><strong>When Preparing Excel Files &#8212; Key Things to Consider</strong></h1><p>Excel is powerful, but tricky.</p><p>It&#8217;s the <em>only format</em> that business teams often request because:</p><ul><li><p>They want filters</p></li><li><p>They want pivot tables</p></li><li><p>They want colors</p></li><li><p>They want formulas</p></li><li><p>They want multiple sheets</p></li></ul><p>But as a developer, Excel is the easiest format to accidentally misuse.</p><h2><strong>1. Avoid more than 1 million rows</strong></h2><p>Excel&#8217;s row limit is:</p><ul><li><p><strong>1,048,576 rows</strong></p></li><li><p><strong>16,384 columns</strong></p></li></ul><p>If you exceed &#8594; use CSV instead.</p><h2><strong>2. Avoid embedding large images inside sheets</strong></h2><p>This increases file size like crazy.</p><h2><strong>3. Prefer simple formats rather than heavy styling</strong></h2><p>Excessive styling = slow opening.</p><h2><strong>4. If your Excel will be consumed by a program, avoid:</strong></h2><ul><li><p>merged cells</p></li><li><p>formulas</p></li><li><p>conditional formatting</p></li><li><p>hidden rows</p></li><li><p>auto filters</p></li></ul><p>These break automated parsing.</p><h2><strong>5. If you DO need formulas &#8212; place them carefully</strong></h2><p>Example:</p><pre><code><code>=SUM(A2:A100)
=IF(B2&gt;10, &#8220;High&#8221;,&#8221;Low&#8221;)
</code></code></pre><p>Always document formulas in a separate sheet.</p><h2><strong>6. Use freeze panes for readability</strong></h2><p>Useful for files that business teams use.</p><h2><strong>7. Use Excel libraries correctly in .NET</strong></h2><p>Recommended libraries:</p><ul><li><p>EPPlus</p></li><li><p>ClosedXML</p></li><li><p>NPOI</p></li></ul><p>Each has pros and cons depending on file size &amp; complexity.</p><h2><strong>8. Watch out for Excel&#8217;s weird auto-conversion</strong></h2><p>Excel converts:</p><ul><li><p>&#8220;1-2&#8221; into a date</p></li><li><p>&#8220;0012&#8221; into &#8220;12&#8221;</p></li><li><p>&#8220;TRUE&#8221; into boolean</p></li><li><p>&#8220;1.5E5&#8221; into exponential</p></li><li><p>long numbers like credit-card digits into scientific notation</p></li></ul><p>If the content is sensitive &#8594; <strong>store as text</strong> intentionally.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1><strong>Final Comparison &#8212; Choosing the Right Format (Simple Mental Model)</strong></h1><h3>&#10004; Choose <strong>CSV</strong> when:</h3><ul><li><p>You have large or flat data</p></li><li><p>You don&#8217;t need formatting</p></li><li><p>You want fastest export &amp; import</p></li><li><p>You want lightweight &amp; universal</p></li></ul><h3>&#10004; Choose <strong>JSON</strong> when:</h3><ul><li><p>You need nested structure</p></li><li><p>You are interacting with APIs</p></li><li><p>You need config files or metadata</p></li><li><p>You need both human &amp; machine readability</p></li></ul><h3>&#10004; Choose <strong>Excel</strong> when:</h3><ul><li><p>Business users are the target</p></li><li><p>You need formulas/charts</p></li><li><p>You need multiple sheets</p></li><li><p>You want filters, pivots, styling</p></li></ul><div><hr></div><h1><strong>The Ultimate One-Line Rules</strong></h1><h3>CSV:</h3><blockquote><p>Use when speed matters. Flat data only.</p></blockquote><h3>JSON:</h3><blockquote><p>Use when structure matters. Machines + APIs.</p></blockquote><h3>Excel:</h3><blockquote><p>Use when humans matter. Reporting + visualization.</p></blockquote><div><hr></div><h1>Closing Note</h1><p>Choosing the right file format is a subtle but crucial decision in every project.<br>Knowing the <strong>strengths</strong>, <strong>limits</strong>, and <strong>preparation rules</strong> of CSV, JSON, and Excel makes you a better developer &#8212; especially in backend, ETL, reporting, or API design.</p>]]></content:encoded></item><item><title><![CDATA[The Great C# Async Universe — Clearing the Confusion Around Task, ValueTask, async/await, IEnumerable & IAsyncEnumerable]]></title><description><![CDATA[A no-nonsense guide that finally makes everything click.]]></description><link>https://dotnetfullstackdev.substack.com/p/the-great-c-async-universe-clearing</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/the-great-c-async-universe-clearing</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Sun, 30 Nov 2025 12:10:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2rUV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;re new to C#, the async world feels like a galaxy full of mysterious planets:</p><ul><li><p><strong>Task</strong></p></li><li><p><strong>ValueTask</strong></p></li><li><p><strong>async / await</strong></p></li><li><p><strong>IEnumerable</strong></p></li><li><p><strong>IAsyncEnumerable</strong></p></li><li><p><strong>yield return</strong></p></li><li><p><strong>yield break</strong></p></li><li><p><strong>ThreadPool</strong></p></li><li><p><strong>SynchronizationContext</strong></p></li><li><p><strong>ConfigureAwait</strong></p></li></ul><div class="pullquote"><p><em><strong>For content overview videos<br><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></em></p></div><p>All floating around, all connected somehow, yet nobody clearly explains <em>how</em>.</p><p>Today, we&#8217;ll clear the entire sky &#8212; not with theory, but with <strong>real-life analogies</strong> and <strong>C# code</strong> that make everything stick forever.</p><p>Imagine this blog as a guided tour through the C# async universe.</p><div><hr></div><h1><strong>1 &#8212; The Core Idea: Synchronous vs Asynchronous</strong></h1><p>Let&#8217;s begin with the root question:</p><h3>Why do we even need async?</h3><p>Real-world analogy:</p><p>You walk into a restaurant.<br>The chef cooks one order at a time.<br>If someone orders biryani (takes 30 minutes), the rest wait.</p><p>That&#8217;s <strong>synchronous execution</strong> &#8212; one thing at a time.</p><p>Async is like:</p><p>Chef puts biryani on the stove &#8594; while it cooks, he makes dosa &#8594; then he makes chai &#8594; meanwhile biryani gets ready.</p><p>Chef is not idle.</p><p>Async != faster.<br>Async = <strong>not blocking the thread while waiting</strong>.</p><p>This keeps your app responsive and scalable.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2rUV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2rUV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2rUV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1802840,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/180310277?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2rUV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!2rUV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb540b7e0-c736-4326-bc55-d21baf2a044c_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h1><strong>2 &#8212; What Is a Task?</strong></h1><p>A <strong>Task</strong> represents a <em>promise</em> of a result in the future.</p><p>It&#8217;s like:</p><blockquote><p>&#8220;I started making your pizza. I&#8217;ll notify you when it&#8217;s done.&#8221;</p></blockquote><p>Task is a <strong>box</strong> that <em>will contain</em> the result eventually.</p><pre><code><code>Task&lt;int&gt; CalculateAsync()
{
    return Task.FromResult(42);
}
</code></code></pre><h3>Task is for async operations that:</h3><ul><li><p>run on another thread</p></li><li><p>depend on I/O</p></li><li><p>return later</p></li><li><p>complete in future</p></li></ul><div><hr></div><h1><strong>3 &#8212; async / await &#8212; The Storybook Explanation</strong></h1><p><code>async</code> is a <strong>keyword for marking a method that can &#8220;pause&#8221;</strong>.</p><p><code>await</code> is a <strong>keyword that pauses without blocking the thread</strong>.</p><p>Real-life analogy:</p><p>You order tea.<br>While tea is boiling, you scroll Instagram.<br>When the kettle whistles, you continue.</p><p>That is <code>await</code>.</p><pre><code><code>public async Task&lt;int&gt; GetNumberAsync()
{
    await Task.Delay(1000); // not blocking
    return 10;
}
</code></code></pre><p>The compiler converts async methods into state machines.</p><div><hr></div><h1><strong>4 &#8212; ValueTask &#8212; When Task Is Too Heavy</strong></h1><p><code>Task</code> is a class &#8594; reference type &#8594; heap allocation.</p><p>If the result is often <strong>already available</strong>, using a full Task object is inefficient.</p><p>Example:</p><pre><code><code>public Task&lt;bool&gt; IsOnlineAsync() =&gt; Task.FromResult(true);
</code></code></pre><p>This allocates a Task for no reason.</p><p><code>ValueTask</code> solves this:</p><pre><code><code>public ValueTask&lt;bool&gt; IsOnlineAsync() =&gt; new(true);
</code></code></pre><p>Benefits:</p><ul><li><p>Avoids allocations</p></li><li><p>Faster in high-performance code</p></li></ul><p>But:</p><ul><li><p>More complex</p></li><li><p>Should be used in hot paths only</p></li><li><p>Avoid returning ValueTask from public libraries (unless needed)</p></li></ul><p>Real-life analogy:</p><p>Task &#8594; costly UPS shipping box.<br>ValueTask &#8594; envelope.<br>Use big box only when necessary.</p><div><hr></div><h1><strong>5 &#8212; IEnumerable &#8212; Synchronous Streaming</strong></h1><p><code>IEnumerable&lt;T&gt;</code> lets you <strong>return items one-by-one</strong>, not the whole list.</p><pre><code><code>public IEnumerable&lt;int&gt; GetNumbers()
{
    yield return 1;
    yield return 2;
    yield return 3;
}
</code></code></pre><p>This is <em>synchronous</em> streaming.</p><p>Real-life analogy:</p><p>You call a friend and ask him to read a book.<br>He reads line by line, one at a time.<br>You get each line immediately.</p><p>Better for:</p><ul><li><p>large sets</p></li><li><p>streaming data</p></li><li><p>pipeline transformations</p></li></ul><div><hr></div><h1><strong>6 &#8212; IAsyncEnumerable &#8212; Asynchronous Streaming</strong></h1><p>But what if each item requires waiting?</p><p>Example:<br>Fetching 1 million records from DB but each chunk is I/O.</p><p><code>IAsyncEnumerable&lt;T&gt;</code> streams items <strong>with async pauses</strong>.</p><pre><code><code>public async IAsyncEnumerable&lt;int&gt; GetNumbersAsync()
{
    await Task.Delay(100);
    yield return 1;

    await Task.Delay(100);
    yield return 2;
}
</code></code></pre><p>Consume it using <code>await foreach</code>:</p><pre><code><code>await foreach (var n in GetNumbersAsync())
{
    Console.WriteLine(n);
}
</code></code></pre><p>Real-life analogy:</p><p>Friend is reading a book to you but takes a sip of water between lines.</p><p>You don&#8217;t block.<br>You wait asynchronously.</p><div><hr></div><h1><strong>7 &#8212; yield return and yield break</strong></h1><p>These keywords create an iterator (state machine).</p><h3>yield return</h3><p>Return next item <strong>without building a full list</strong>.</p><h3>yield break</h3><p>Stop the sequence.</p><pre><code><code>public IEnumerable&lt;int&gt; Example()
{
    yield return 1;
    yield return 2;
    yield break;
    yield return 3; // never executed
}
</code></code></pre><p>Works for async too, in combination with <code>await</code>.</p><div><hr></div><h1><strong>8 &#8212; ThreadPool vs async</strong></h1><p>Async does <strong>not</strong> create new threads.</p><p>It only <em>frees</em> the current thread during waiting operations.</p><p>CPU-bound work should use:</p><pre><code><code>await Task.Run(() =&gt; HeavyWork());
</code></code></pre><p>I/O-bound work should <strong>never</strong> use Task.Run.</p><div><hr></div><h1><strong>9 &#8212; ConfigureAwait(false)</strong></h1><p>This is important for libraries.</p><p>It tells the runtime:</p><blockquote><p>&#8220;I don&#8217;t need to resume on original context.&#8221;</p></blockquote><p>Useful in:</p><ul><li><p>libraries</p></li><li><p>background services</p></li><li><p>console apps</p></li></ul><p>Not recommended in ASP.NET Core (context is already empty).</p><div><hr></div><h1><strong>10 &#8212; Common Mistakes &amp; Fixes</strong></h1><h3>Mistake 1: Using <code>.Result</code> or <code>.Wait()</code></h3><pre><code><code>var data = GetDataAsync().Result; // deadlock risk!
</code></code></pre><h3>Correct:</h3><pre><code><code>var data = await GetDataAsync();
</code></code></pre><div><hr></div><h3>Mistake 2: Overusing async/await on simple methods</h3><pre><code><code>public async Task&lt;int&gt; Add() =&gt; 5 + 10; // unnecessary
</code></code></pre><h3>Correct:</h3><pre><code><code>public int Add() =&gt; 5 + 10;
</code></code></pre><div><hr></div><h3>Mistake 3: Mixing I/O and CPU async incorrectly</h3><pre><code><code>await Task.Run(() =&gt; _db.Save()); // wrong
</code></code></pre><h3>Correct:</h3><pre><code><code>await _db.SaveAsync();
</code></code></pre><div><hr></div><h1><strong>Bringing It All Together &#8212; The Final Picture</strong></h1><p>Here&#8217;s the big mental map:</p><pre><code><code>IEnumerable&lt;T&gt;         &#8594; sync streaming
IAsyncEnumerable&lt;T&gt;    &#8594; async streaming
Task                   &#8594; async result in future (heap)
ValueTask              &#8594; lightweight async result (stack/struct)
async/await            &#8594; pause &amp; resume machine
yield                  &#8594; streaming without list
await foreach          &#8594; async streaming loop
</code></code></pre><p>Everything fits neatly.</p><h2><strong>Keep the Momentum Going &#8212; Support the Journey</strong></h2><blockquote><p><em>If this post helped you level up or added value to your day, feel free to fuel the next one &#8212; <strong><a href="https://buymeacoffee.com/dotnetfullstackdev">Buy Me a Coffee</a></strong> powers deeper breakdowns, real-world examples, and crisp technical storytelling.</em></p></blockquote><div><hr></div><h1><strong>Final Thought &#8212; Async Isn&#8217;t Hard. It Was Just Never Explained Simply.</strong></h1><p>Once you know:</p><ul><li><p>what Task is (a promise)</p></li><li><p>what async/await does (pause &amp; resume)</p></li><li><p>when ValueTask matters (performance)</p></li><li><p>how IEnumerable streams data</p></li><li><p>how IAsyncEnumerable streams asynchronously</p></li></ul><p>&#8230;then everything starts making sense.</p><p>You stop fearing async.<br>You start <em>using</em> it properly.<br>You become a faster, smarter, clearer C# developer.</p>]]></content:encoded></item><item><title><![CDATA[Hazelcast + .NET: The Complete Beginner-to-Advanced Guide (with Real-Time Analogies & Code Samples)]]></title><description><![CDATA[If you&#8217;ve ever wished your .NET applications could:]]></description><link>https://dotnetfullstackdev.substack.com/p/hazelcast-net-the-complete-beginner</link><guid isPermaLink="false">https://dotnetfullstackdev.substack.com/p/hazelcast-net-the-complete-beginner</guid><dc:creator><![CDATA[DotNet Full Stack Dev]]></dc:creator><pubDate>Fri, 28 Nov 2025 17:55:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lC1o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><br>If you&#8217;ve ever wished your .NET applications could:</p><ul><li><p>scale faster</p></li><li><p>share cache across multiple servers</p></li><li><p>handle millions of operations per second</p></li><li><p>store data <em>in-memory</em> but <em>distributed</em></p></li><li><p>outperform Redis in cluster scenarios</p></li><li><p>or build real-time systems like Uber, Netflix, or Shopify</p></li></ul><p>&#8230;then <strong>Hazelcast</strong> is one of the most powerful tools you can learn.</p><p>This guide explains Hazelcast in simple language, with a real-world analogy, and gives you step-by-step .NET code examples.</p><div class="pullquote"><p>For content overview videos  <br><strong><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></p></div><div><hr></div><h1><strong>What is Hazelcast (In the Most Human Language Possible)?</strong></h1><p>Hazelcast is a <strong>distributed in-memory data grid (IMDG)</strong>.</p><p>In plain terms:</p><blockquote><p>It&#8217;s a superfast, shared in-memory storage system where multiple apps and servers can store and access data together.</p></blockquote><p>Think of it as:</p><ul><li><p>A <strong>cluster of RAM</strong> shared across machines</p></li><li><p>A very fast <strong>distributed cache</strong></p></li><li><p>A <strong>compute grid</strong> for parallel workloads</p></li><li><p>A <strong>message/event platform</strong></p></li></ul><p>Hazelcast is used for:</p><ul><li><p>Distributed caching</p></li><li><p>Session storage</p></li><li><p>Real-time analytics</p></li><li><p>Event streaming</p></li><li><p>Pub/sub messaging</p></li><li><p>Microservice coordination</p></li><li><p>Fast key-value storage</p></li></ul><p>It also has built-in:</p><ul><li><p>CP Subsystems (for strong consistency)</p></li><li><p>Cluster discovery</p></li><li><p>Automatic failover</p></li><li><p>Persistence (if needed)</p></li><li><p>SQL engine</p></li></ul><p>And it integrates beautifully with <strong>.NET clients</strong>.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lC1o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lC1o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lC1o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1759868,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://dotnetfullstackdev.substack.com/i/180194255?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lC1o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!lC1o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9d32e0-5218-4853-ab17-da57a66175ef_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h1><strong>Real-Time Analogy: Pizza Counters in a Big Restaurant</strong></h1><p>Imagine you run a massive restaurant with 20 pizza counters.<br>Customers order pizzas nonstop.</p><p>If you stored all orders and workflow in one counter&#8217;s notebook:</p><ul><li><p>Orders become slow</p></li><li><p>One counter becomes the bottleneck</p></li><li><p>A crash wipes everything</p></li><li><p>Other counters have no idea what&#8217;s going on</p></li></ul><p>Hazelcast solves this by giving:</p><ul><li><p>One big shared &#8220;in-memory board&#8221;</p></li><li><p>All pizza counters read &amp; write together</p></li><li><p>If one counter crashes, other counters still hold the data</p></li><li><p>Notes (data) are replicated everywhere</p></li><li><p>Workload is distributed</p></li></ul><p>That&#8217;s Hazelcast &#8212; <strong>a shared memory + shared computing system</strong> for your application servers.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><h1><strong>Hazelcast Architecture in Simple Words</strong></h1><p>Hazelcast consists of:</p><h3><strong>1&#65039;&#8419; Cluster (Servers / Members)</strong></h3><ul><li><p>Java server nodes</p></li><li><p>Store data in memory</p></li><li><p>Replicate partitions</p></li><li><p>Perform compute operations</p></li></ul><h3><strong>2&#65039;&#8419; Clients</strong></h3><p>Your .NET application connects to the cluster as a <strong>client</strong> using Hazelcast .NET client libraries.</p><p>Clients can:</p><ul><li><p>Get/put data into distributed maps</p></li><li><p>Publish/subscribe to topics</p></li><li><p>Execute queries</p></li><li><p>Run distributed locks</p></li><li><p>Use distributed queues</p></li><li><p>Execute entry processors</p></li></ul><h3><strong>3&#65039;&#8419; Data Structures</strong></h3><p>Hazelcast provides:</p><ul><li><p><strong>IMap</strong> (Distributed Dictionary)</p></li><li><p><strong>MultiMap</strong></p></li><li><p><strong>IList / ISet</strong></p></li><li><p><strong>IQueue / ITopic</strong></p></li><li><p><strong>CP Subsystem for locks/semaphores</strong></p></li></ul><div><hr></div><h1><strong>Setting up Hazelcast Server</strong></h1><h3><strong>Step 1 &#8212; Download Hazelcast</strong></h3><p>Download Hazelcast standalone server:</p><p>&#128073; https://hazelcast.com/open-source-projects/download/</p><p>Extract and run:</p><pre><code><code>bin/hz-start
</code></code></pre><p>You should see logs like:</p><pre><code><code>Members {size:1, ver:1} [
    Member 1: 192.168.1.10:5701 - f5c7a9a2
]
</code></code></pre><p>Your Hazelcast cluster (1 member) is ready.</p><div><hr></div><h1><strong>Using Hazelcast in .NET: Step-by-Step</strong></h1><h3><strong>Install the .NET Client</strong></h3><p>Use NuGet:</p><pre><code><code>dotnet add package Hazelcast.Net
</code></code></pre><p>Or via Visual Studio:</p><pre><code><code>Install-Package Hazelcast.Net
</code></code></pre><div><hr></div><h1><strong>Connecting .NET to Hazelcast Cluster</strong></h1><pre><code><code>using Hazelcast;

var options = new HazelcastOptions
{
    ClusterName = &#8220;dev&#8221;
};

options.Networking.Addresses.Add(&#8221;127.0.0.1:5701&#8221;);

var client = await HazelcastClientFactory.StartNewClientAsync(options);

Console.WriteLine(&#8221;&#10004; Connected to Hazelcast!&#8221;);
</code></code></pre><p>This creates a .NET client that works like a distributed memory user.</p><div><hr></div><h1><strong>Distributed Map (IMap) &#8212; The Most Powerful Structure</strong></h1><p>Hazelcast&#8217;s <code>IMap</code> is basically:</p><blockquote><p>A Dictionary&lt;TKey, TValue&gt; that lives across multiple servers and replicates itself.</p></blockquote><h3><strong>Writing data to the map</strong></h3><pre><code><code>var map = await client.GetMapAsync&lt;string, string&gt;(&#8221;users&#8221;);

await map.PutAsync(&#8221;user:1&#8221;, &#8220;Bhargavi&#8221;);
await map.PutAsync(&#8221;user:2&#8221;, &#8220;Kishore&#8221;);

Console.WriteLine(&#8221;Users added!&#8221;);
</code></code></pre><h3><strong>Reading data</strong></h3><pre><code><code>var value = await map.GetAsync(&#8221;user:1&#8221;);
Console.WriteLine(value); // Bhargavi
</code></code></pre><h3><strong>Updating</strong></h3><pre><code><code>await map.PutAsync(&#8221;user:1&#8221;, &#8220;Bhargavi Mojala&#8221;);
</code></code></pre><h3><strong>Deleting</strong></h3><pre><code><code>await map.RemoveAsync(&#8221;user:2&#8221;);
</code></code></pre><p>Hazelcast automatically:</p><ul><li><p>Partitions the map</p></li><li><p>Replicates data</p></li><li><p>Balances load</p></li><li><p>Handles failover</p></li></ul><p>Your .NET code stays simple.</p><div><hr></div><h1><strong>Distributed Locking With Hazelcast</strong></h1><p>You can build concurrency-safe distributed systems.</p><pre><code><code>var lockObj = await client.GetLockAsync(&#8221;payment-lock&#8221;);

await lockObj.LockAsync();

try
{
    Console.WriteLine(&#8221;Processing payment...&#8221;);
    await Task.Delay(2000);
}
finally
{
    await lockObj.UnlockAsync();
}
</code></code></pre><p>This ensures <strong>only one server</strong> in your cluster processes a &#8220;payment&#8221; at a time.</p><p>Ideal for:</p><ul><li><p>Financial transactions</p></li><li><p>Unique ID generation</p></li><li><p>Inventory updates</p></li><li><p>File processing</p></li></ul><div><hr></div><h1><strong>Publish/Subscribe Messaging</strong></h1><p>Hazelcast includes lightweight message broadcasting.</p><pre><code><code>var topic = await client.GetTopicAsync&lt;string&gt;(&#8221;alerts&#8221;);

await topic.SubscribeAsync(msg =&gt;
{
    Console.WriteLine(&#8221;Received alert: &#8220; + msg);
});

await topic.PublishAsync(&#8221;High CPU usage!&#8221;);
</code></code></pre><p>This is simpler than RabbitMQ/Kafka for internal events.</p><div><hr></div><h1><strong>Querying Data Using Predicates</strong></h1><p>Hazelcast supports distributed querying.</p><p>Store complex objects:</p><pre><code><code>public class Product
{
    public int Id { get; set; }
    public string Category { get; set; }
    public double Price { get; set; }
}
</code></code></pre><p>Put into map:</p><pre><code><code>var products = await client.GetMapAsync&lt;int, Product&gt;(&#8221;products&#8221;);

await products.PutAsync(1, new Product { Id = 1, Category = &#8220;Electronics&#8221;, Price = 25000 });
await products.PutAsync(2, new Product { Id = 2, Category = &#8220;Clothing&#8221;, Price = 800 });
</code></code></pre><p>Query:</p><pre><code><code>var expensiveItems = await products.GetValuesAsync(
    Predicates.GreaterThan(&#8221;Price&#8221;, 5000)
);
</code></code></pre><p>This runs the filter <strong>across the cluster</strong> and sends minimal data back.</p><div><hr></div><h1><strong>Hazelcast as Distributed Cache for .NET APIs</strong></h1><p>In ASP.NET Core, register Hazelcast client using DI:</p><pre><code><code>builder.Services.AddSingleton(async _ =&gt;
{
    var options = new HazelcastOptions();
    options.Networking.Addresses.Add(&#8221;127.0.0.1:5701&#8221;);

    return await HazelcastClientFactory.StartNewClientAsync(options);
});
</code></code></pre><p>Use it inside a controller:</p><pre><code><code>[ApiController]
[Route(&#8221;api/products&#8221;)]
public class ProductsController : ControllerBase
{
    private readonly IHazelcastClient _client;

    public ProductsController(IHazelcastClient client)
    {
        _client = client;
    }

    [HttpGet(&#8221;{id}&#8221;)]
    public async Task&lt;IActionResult&gt; Get(int id)
    {
        var cache = await _client.GetMapAsync&lt;int, Product&gt;(&#8221;products&#8221;);

        if (await cache.ContainsKeyAsync(id))
        {
            return Ok(await cache.GetAsync(id));
        }

        // simulate DB call
        var product = new Product { Id = id, Category = &#8220;Electronics&#8221;, Price = 1200 };

        await cache.PutAsync(id, product);

        return Ok(product);
    }
}
</code></code></pre><p>This creates a <strong>shared distributed cache</strong> across your whole cluster.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://dotnetfullstackdev.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://dotnetfullstackdev.substack.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Final Thoughts &#8212; Hazelcast Makes .NET <em>Superhuman Fast</em></h1><p>Hazelcast is one of those tools that looks complicated from outside but is shockingly simple once you start.</p><p>With just a few lines of C#:</p><ul><li><p>Your app becomes distributed</p></li><li><p>Your memory becomes scalable</p></li><li><p>Your operations become fail-safe</p></li><li><p>Your cache becomes lightning fast</p></li><li><p>Your architecture becomes cloud-ready</p></li></ul><p>And best of all &#8212;</p><p><strong>You don&#8217;t need to rewrite your .NET app</strong>.<br>Just plug Hazelcast in.</p><p>You now know:</p><ul><li><p>What Hazelcast is</p></li><li><p>How clusters work</p></li><li><p>How .NET connects to it</p></li><li><p>How to use maps, locks, queues, topics</p></li><li><p>How to build distributed systems using simple C#</p></li></ul><div class="pullquote"><p>For content overview videos  <br><strong><a href="https://www.youtube.com/@DotNetFullstackDev">https://www.youtube.com/@DotNetFullstackDev</a></strong></p></div>]]></content:encoded></item></channel></rss>