<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dotnet</title>
    <description>The latest articles tagged 'dotnet' on DEV Community.</description>
    <link>https://dev.to/t/dotnet</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/dotnet"/>
    <language>en</language>
    <item>
      <title>StructoFox: Flowcharts-&gt;structograms-&gt;code</title>
      <dc:creator>Doombug75</dc:creator>
      <pubDate>Thu, 16 Jul 2026 18:37:19 +0000</pubDate>
      <link>https://dev.to/doombug75/structofox-flowcharts-structograms-code-3hbn</link>
      <guid>https://dev.to/doombug75/structofox-flowcharts-structograms-code-3hbn</guid>
      <description>&lt;p&gt;I made a small tool for planning code as diagrams instead of writing it top-down, and just put out the first build.&lt;/p&gt;

&lt;p&gt;It makes it easy to quickly create flowcharts, and put them into multi format .pdfs, .tifs or just images for your presentations.&lt;br&gt;
Code functionality: let the tool automatically turn the Flowcharts into structograms, which can easily be turned into a code skeleton in 13 languages - without any AI.&lt;br&gt;
Or you can activate the optional AI plugin (included and also Oppen-Source), connect API or local (llm/ollama) models, and let those generate code in whole projects.&lt;/p&gt;

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

&lt;p&gt;The conversion from chart to structogram can also act as a structure check — jumps that can't be expressed as clean nested blocks get flagged instead of silently miscompiled.&lt;/p&gt;

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

&lt;p&gt;It's .NET 10 + Avalonia, runs on Windows/Linux/macOS, MIT licensed. Beta phase, planned features all work and known bugs are fixed.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/StructoFox/StructoFox" rel="noopener noreferrer"&gt;https://github.com/StructoFox/StructoFox&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find some real bugs, let me know. This was made for all those who teach or learn flowcharts and structograms, and for all those who simply want to print out the one or other huge colorful flowchart. :o)&lt;/p&gt;

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

</description>
      <category>showdev</category>
      <category>dotnet</category>
      <category>opensource</category>
      <category>education</category>
    </item>
    <item>
      <title>Asdamir: Scaffold a working .NET 10 + Blazor enterprise app in 2 commands</title>
      <dc:creator>Orhan özşahin</dc:creator>
      <pubDate>Thu, 16 Jul 2026 18:18:31 +0000</pubDate>
      <link>https://dev.to/oozsahin/asdamir-scaffold-a-working-net-10-blazor-enterprise-app-in-2-commands-3ci3</link>
      <guid>https://dev.to/oozsahin/asdamir-scaffold-a-working-net-10-blazor-enterprise-app-in-2-commands-3ci3</guid>
      <description>&lt;p&gt;I've been building &lt;strong&gt;Asdamir&lt;/strong&gt; for the past several months — a CLI + framework that scaffolds production-shaped .NET apps. Just shipped 1.3, so I'm writing it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;asdamir new app MyApp --mode free&lt;/code&gt; gives you a working Blazor Server + API app with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication + RBAC&lt;/li&gt;
&lt;li&gt;Menus and navigation&lt;/li&gt;
&lt;li&gt;Localization (tr/en/ru)&lt;/li&gt;
&lt;li&gt;A journaled migration runner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It creates the database, applies migrations, and writes the dev secrets — then &lt;code&gt;./restart-myapp.sh&lt;/code&gt; and you're logged in. No manual wiring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a feature is one command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asdamir new feature Product &lt;span class="nt"&gt;--fields&lt;/span&gt; &lt;span class="s2"&gt;"Name:string,Price:decimal,Stock:int"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you: entity + CRUD page + menu entry + permissions + migration — applied automatically. Restart the app and the page is in the menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full quick-start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install the CLI (once)&lt;/span&gt;
dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; Asdamir.Tools

&lt;span class="c"&gt;# create an app in any empty directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-apps &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-apps
asdamir new app DemoApp &lt;span class="nt"&gt;--mode&lt;/span&gt; free

&lt;span class="c"&gt;# run it&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;DemoApp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./restart-demoapp.sh
&lt;span class="c"&gt;# → https://localhost:7010, sign in with the starter admin&lt;/span&gt;

&lt;span class="c"&gt;# add a feature&lt;/span&gt;
asdamir new feature Product &lt;span class="nt"&gt;--fields&lt;/span&gt; &lt;span class="s2"&gt;"Name:string,Price:decimal,Stock:int"&lt;/span&gt;
./restart-demoapp.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prerequisites: .NET 10 SDK + SQL Server (localhost:1433).&lt;/p&gt;

&lt;h2&gt;
  
  
  Free mode is genuinely standalone
&lt;/h2&gt;

&lt;p&gt;No license check, no control plane, no phone-home. The generated app owns its own database and issues its own JWTs. It's open-core on NuGet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asdamir.Core&lt;/strong&gt; — models, multi-tenancy, JWT/AES-GCM, validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asdamir.Data&lt;/strong&gt; — Dapper repositories, journaled migrations, outbox&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asdamir.Web&lt;/strong&gt; — Blazor components, security middleware (CSP nonce, rate-limit), localization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asdamir.Payments&lt;/strong&gt; — Paddle (MoR) + crypto rails, webhook signature verification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asdamir.Tools&lt;/strong&gt; — the CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a commercial control plane (multi-app RBAC/config/monitoring) but you never need it for a single app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I've been doing enterprise .NET for ~20 years, and this came out of rebuilding the same auth/RBAC/menu/i18n plumbing on project after project. Every new app started with the same two weeks of boilerplate. Asdamir is that boilerplate, generated in two commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'd love feedback
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does the free-mode flow actually work on your machine?&lt;/li&gt;
&lt;li&gt;Is the scaffolding opinionated in useful ways, or annoying ways?&lt;/li&gt;
&lt;li&gt;What would stop you from using it on a real project?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Site: &lt;a href="https://asdamir.com" rel="noopener noreferrer"&gt;asdamir.com&lt;/a&gt; · Docs: &lt;a href="https://docs.asdamir.com" rel="noopener noreferrer"&gt;docs.asdamir.com&lt;/a&gt; · GitHub: &lt;a href="https://github.com/asdamir-framework/asdamir" rel="noopener noreferrer"&gt;asdamir-framework/asdamir&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer anything in the comments.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>blazor</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Design Patterns in C# Part 2: Structural Patterns - Adapter, Decorator, Facade, and Composite</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:14:32 +0000</pubDate>
      <link>https://dev.to/manoharij/design-patterns-in-c-part-2-structural-patterns-adapter-decorator-facade-and-composite-25mh</link>
      <guid>https://dev.to/manoharij/design-patterns-in-c-part-2-structural-patterns-adapter-decorator-facade-and-composite-25mh</guid>
      <description>&lt;p&gt;This is Part 2 of a three-part design patterns series built for interview prep. Part 1 covered Creational patterns - how objects get created. This part covers Structural patterns - how objects and classes are composed into larger structures. Same format throughout: the problem each pattern solves, an analogy, a before-and-after code example, and honest guidance on when it's actually worth the added complexity.&lt;/p&gt;

&lt;p&gt;Structural patterns include Adapter, Decorator, Facade, Composite, Proxy, and Bridge. This post covers the four that come up most often in real interviews and real code: Adapter, Decorator, Facade, and Composite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Adapter
&lt;/h2&gt;

&lt;p&gt;The problem Adapter solves is making two interfaces that were never designed to work together actually work together, without modifying either one.&lt;/p&gt;

&lt;p&gt;Think of a physical power plug adapter. A US laptop charger and a European wall socket are both perfectly functional on their own, but their shapes are incompatible. Nobody rewires the wall or rebuilds the charger - a small adapter sits between them, translating one shape into the other. Adapter does the same thing in code: it translates one interface into another without changing either side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The interface your code already expects&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IPaymentProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A third-party library with an incompatible interface&lt;/span&gt;
&lt;span class="c1"&gt;// you cannot modify - this is someone else's code&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyPaymentGateway&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MakeTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amountInCents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Processing &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amountInCents&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; cents"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITHOUT Adapter - the rest of the codebase has to&lt;/span&gt;
&lt;span class="c1"&gt;// know about LegacyPaymentGateway's specific method&lt;/span&gt;
&lt;span class="c1"&gt;// signature and unit conventions directly&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;LegacyPaymentGateway&lt;/span&gt; &lt;span class="n"&gt;_gateway&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// every caller needs to remember to convert&lt;/span&gt;
        &lt;span class="c1"&gt;// dollars to cents, and call MakeTransaction&lt;/span&gt;
        &lt;span class="c1"&gt;// specifically - tightly coupled to this one&lt;/span&gt;
        &lt;span class="c1"&gt;// library's exact shape&lt;/span&gt;
        &lt;span class="n"&gt;_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeTransaction&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITH Adapter - the incompatibility is isolated&lt;/span&gt;
&lt;span class="c1"&gt;// to one small class&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyPaymentAdapter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPaymentProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;LegacyPaymentGateway&lt;/span&gt; &lt;span class="n"&gt;_gateway&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LegacyPaymentAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LegacyPaymentGateway&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_gateway&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the unit conversion and method name&lt;/span&gt;
        &lt;span class="c1"&gt;// mismatch lives HERE, in exactly one place&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amountInCents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amountInCents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The rest of the codebase depends only on&lt;/span&gt;
&lt;span class="c1"&gt;// IPaymentProcessor - it never knows LegacyPaymentGateway&lt;/span&gt;
&lt;span class="c1"&gt;// exists at all&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPaymentProcessor&lt;/span&gt; &lt;span class="n"&gt;_processor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CheckoutService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPaymentProcessor&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LegacyPaymentAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LegacyPaymentGateway&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;checkout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CheckoutService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;49.99m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adapter earns its place specifically around third-party libraries, legacy code, or any external dependency whose interface doesn't match what the rest of the codebase expects and can't be changed directly. It's not worth introducing between two pieces of code that are both already under direct control - in that case, simply changing one interface to match the other is the more direct fix, with no adapter layer needed at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: Decorator
&lt;/h2&gt;

&lt;p&gt;The problem Decorator solves is adding behavior to a single object instance dynamically, without modifying its class, without affecting other instances of that same class, and without relying on subclassing.&lt;/p&gt;

&lt;p&gt;Think of a plain coffee, with milk added, then a shot of caramel added on top of that. Each addition wraps the drink before it, adding its own contribution, without needing a separate class for every possible combination - CoffeeWithMilk, CoffeeWithCaramel, CoffeeWithMilkAndCaramel, and so on infinitely. Decorator wraps objects the same way, layer by layer, at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The base interface&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICoffee&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlainCoffee&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICoffee&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2.00m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Coffee"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITHOUT Decorator - a new subclass for every&lt;/span&gt;
&lt;span class="c1"&gt;// possible combination of add-ons&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeWithMilk&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlainCoffee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeWithCaramel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlainCoffee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeWithMilkAndCaramel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlainCoffee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Adding a third add-on option means the number of&lt;/span&gt;
&lt;span class="c1"&gt;// subclasses needed grows combinatorially&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITH Decorator - each add-on is a small wrapper&lt;/span&gt;
&lt;span class="c1"&gt;// class that can be combined at runtime, in any order&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeeDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICoffee&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICoffee&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;CoffeeDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ICoffee&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MilkDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoffeeDecorator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MilkDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ICoffee&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" + milk"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaramelDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoffeeDecorator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CaramelDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ICoffee&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.75m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_coffee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" + caramel"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Combine any way, at runtime, without a single&lt;/span&gt;
&lt;span class="c1"&gt;// new subclass for the combination&lt;/span&gt;
&lt;span class="n"&gt;ICoffee&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CaramelDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MilkDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlainCoffee&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Coffee + milk + caramel&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;        &lt;span class="c1"&gt;// 3.25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is not just a textbook exercise - it's how the .NET Stream classes actually work. &lt;code&gt;new GZipStream(new FileStream(path, FileMode.Open), CompressionMode.Compress)&lt;/code&gt; wraps a FileStream with compression behavior, and more decorators like encryption or buffering can wrap that further, each layer adding one capability without changing any of the classes underneath.&lt;/p&gt;

&lt;p&gt;Decorator earns its place when behavior needs to be added or removed from individual object instances at runtime, in combinations that would otherwise require an unmanageable number of subclasses. For a fixed, small set of variations known entirely at compile time, straightforward inheritance or simple composition without the full decorator pattern is often simpler and easier to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Facade
&lt;/h2&gt;

&lt;p&gt;The problem Facade solves is providing one simple, unified interface to a subsystem made up of many complex, interacting parts, so calling code doesn't need to understand or coordinate all of those parts directly.&lt;/p&gt;

&lt;p&gt;Think of a car's ignition. Turning the key, or pressing the start button, triggers fuel injection, spark timing, starter motor engagement, and a dozen other coordinated subsystems - the driver never manages any of that directly. One simple action hides a genuinely complicated system underneath. Facade provides that same single, simple entry point in code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A genuinely complex subsystem with several&lt;/span&gt;
&lt;span class="c1"&gt;// interacting parts - this complexity is real&lt;/span&gt;
&lt;span class="c1"&gt;// and does not go away, it just gets hidden&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventorySystem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CheckStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// simplified&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentSystem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ChargeCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cardToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// simplified&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShippingSystem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ScheduleDelivery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"TRACK123"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// simplified&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationSystem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;trackingNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Confirmation sent: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trackingNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITHOUT Facade - calling code has to know about&lt;/span&gt;
&lt;span class="c1"&gt;// and correctly coordinate all four systems itself&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrderWithoutFacade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cardToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InventorySystem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentSystem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;shipping&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ShippingSystem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotificationSystem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChargeCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tracking&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shipping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleDelivery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracking&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// this coordination logic now has to be repeated&lt;/span&gt;
    &lt;span class="c1"&gt;// correctly everywhere an order gets placed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WITH Facade - one simple entry point&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderFacade&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;InventorySystem&lt;/span&gt; &lt;span class="n"&gt;_inventory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;PaymentSystem&lt;/span&gt; &lt;span class="n"&gt;_payment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ShippingSystem&lt;/span&gt; &lt;span class="n"&gt;_shipping&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;NotificationSystem&lt;/span&gt; &lt;span class="n"&gt;_notification&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cardToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChargeCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tracking&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_shipping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleDelivery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracking&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Calling code is now trivially simple, and the&lt;/span&gt;
&lt;span class="c1"&gt;// coordination logic exists in exactly one place&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;facade&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderFacade&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;facade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SKU-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tok_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;49.99m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"123 Main St"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"customer@example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Facade earns its place whenever calling code repeatedly needs to coordinate several subsystems in the same sequence - centralizing that coordination in one place prevents the same multi-step logic from being duplicated, and inevitably drifting out of sync, across multiple call sites. It's not about making the underlying subsystem less complex - that complexity is still there - it's about giving callers a simple, correct way to use it without needing to understand all of it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Composite
&lt;/h2&gt;

&lt;p&gt;The problem Composite solves is treating a single object and a group of objects through the exact same interface, so calling code doesn't need separate logic for "one item" versus "a collection of items."&lt;/p&gt;

&lt;p&gt;Think of a file system. A folder can contain files, and it can also contain other folders, which themselves contain files or more folders, arbitrarily deep. When calculating how much space a folder uses, the calculation works identically whether a given item turns out to be a single file or an entire nested folder - each one just needs to be able to answer "how big are you." Composite is exactly this: treating individual objects and groups of objects through one shared interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The shared interface - both leaves and&lt;/span&gt;
&lt;span class="c1"&gt;// composites implement this&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IFileSystemItem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;GetSize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Leaf - a single file, no children&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileItem&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFileSystemItem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;GetSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Composite - a folder, which can contain&lt;/span&gt;
&lt;span class="c1"&gt;// other IFileSystemItems (files OR folders)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FolderItem&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFileSystemItem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFileSystemItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;FolderItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFileSystemItem&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The size of a folder is just the sum of&lt;/span&gt;
    &lt;span class="c1"&gt;// whatever its children report - it does not&lt;/span&gt;
    &lt;span class="c1"&gt;// matter whether each child is a file or&lt;/span&gt;
    &lt;span class="c1"&gt;// another folder, the interface is identical&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;GetSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Building a nested structure&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FolderItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FolderItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"documents"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resume.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cover-letter.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FolderItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photos"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vacation.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"notes.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// One call, correctly recurses through the ENTIRE&lt;/span&gt;
&lt;span class="c1"&gt;// nested structure - no special-casing files vs&lt;/span&gt;
&lt;span class="c1"&gt;// folders anywhere in this calling code&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 3750&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Composite earns its place specifically when a domain is genuinely hierarchical - things that contain other things of the same conceptual type, arbitrarily deep - like file systems, UI component trees, or organizational charts. For a flat, non-nested collection of items with no "contains other items of the same type" relationship, Composite adds a layer of abstraction that isn't solving any real problem, and a plain list is simpler and more honest about the actual shape of the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;p&gt;Adapter isolates an incompatibility to one small class, and earns its place specifically around external dependencies that can't be modified directly - not between two pieces of code already under direct control.&lt;/p&gt;

&lt;p&gt;Decorator adds behavior to individual object instances at runtime, avoiding a combinatorial explosion of subclasses - .NET's own Stream classes are a real, built-in example of this pattern in daily use.&lt;/p&gt;

&lt;p&gt;Facade centralizes coordination logic for a complex subsystem into one simple entry point, without making the underlying subsystem itself any less complex.&lt;/p&gt;

&lt;p&gt;Composite lets calling code treat a single item and a nested group of items identically, and earns its place specifically for genuinely hierarchical domains, not for flat collections with no real containment relationship.&lt;/p&gt;

&lt;p&gt;Every structural pattern covered here is solving a coupling or duplication problem that becomes real friction at some scale - none of them should be reached for preemptively before that friction actually shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Part 3 covers Behavioral patterns - Strategy, Observer, Command, State, and Template Method - the patterns concerned with how objects communicate and share responsibility with each other. This closes out the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Structural patterns solve one consistent underlying question - how should these pieces actually fit together - in four different ways depending on the specific shape of the problem. Adapter reconciles two incompatible interfaces. Decorator adds behavior to individual instances without subclass explosion. Facade simplifies the calling code's relationship to a complex subsystem. Composite treats individual items and nested groups through one shared interface. As with the creational patterns covered in Part 1, each of these adds real indirection that only pays for itself once the specific problem it solves is genuinely present in the code.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog: &lt;a href="https://www.techstackblog.com/post.html?slug=design-patterns-structural-csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=design-patterns-structural-csharp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part 1 of this series (Creational patterns): &lt;a href="https://www.techstackblog.com/post.html?slug=design-patterns-creational-csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=design-patterns-creational-csharp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog: CS Fundamentals: &lt;a href="https://www.techstackblog.com/category.html?cat=cs-fundamentals" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=cs-fundamentals&lt;/a&gt;&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>v0.2.0 - Horus.F5Tts.Onnx - MASSIVE improvements</title>
      <dc:creator>Kasper</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:26:20 +0000</pubDate>
      <link>https://dev.to/nibor1896/v020-horusf5ttsonnx-107c</link>
      <guid>https://dev.to/nibor1896/v020-horusf5ttsonnx-107c</guid>
      <description>&lt;p&gt;&lt;a href="//nibor1896.github.io/Horus.F5Tts.Onnx/"&gt;HORUS.F5TTS.ONNX&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.nuget.org/packages/Horus.F5Tts.Onnx#readme-body-tab" rel="noopener noreferrer"&gt;HORUS.F5TTS.ONNX - NuGet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;v0.2.0 ships FP16 → 10× faster on GPU.&lt;br&gt;
60 ms per denoising step, down from 617. Measured, not vibes.&lt;/p&gt;

&lt;p&gt;Plus: text of any length, real cancellation, resampling built in.&lt;/p&gt;

&lt;p&gt;MIT · 90 tests&lt;br&gt;
dotnet add package Horus.F5Tts.Onnx&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The 8-Byte Lock: Packing a Reader-Writer Lock Into 64 Bits</title>
      <dc:creator>Loïc Baumann</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:01:20 +0000</pubDate>
      <link>https://dev.to/nockawa/the-8-byte-lock-packing-a-reader-writer-lock-into-64-bits-395j</link>
      <guid>https://dev.to/nockawa/the-8-byte-lock-packing-a-reader-writer-lock-into-64-bits-395j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;💡Typhon is an embedded, persistent, ACID database engine written in .NET that speaks the native language of game servers and real-time simulations: entities, components, and systems.&lt;br&gt;&lt;br&gt;
It delivers full transactional safety with MVCC snapshot isolation at sub-microsecond latency, powered by cache-line-aware storage, zero-copy access, and configurable durability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series: A Database That Thinks Like a Game Engine&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/why-building-database-engine-in-csharp/" rel="noopener noreferrer"&gt;Why I'm Building a Database Engine in C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/what-game-engines-know-about-data/" rel="noopener noreferrer"&gt;What Game Engines Know About Data That Databases Forgot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/microsecond-latency-managed-language/" rel="noopener noreferrer"&gt;Microsecond Latency in a Managed Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/deadlock-free-by-construction/" rel="noopener noreferrer"&gt;Deadlock-Free by Construction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/three-durability-modes-one-wal/" rel="noopener noreferrer"&gt;Three Durability Modes, One WAL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/page-cache-that-doesnt-count/" rel="noopener noreferrer"&gt;Building a Page Cache That Doesn't Count&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nockawa.github.io/blog/mvcc-at-microsecond-scale/" rel="noopener noreferrer"&gt;MVCC at Microsecond Scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 8-Byte Lock&lt;/strong&gt; &lt;em&gt;(this post)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Why We Eliminated the Primary Key B+Tree &lt;em&gt;(coming soon)&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/Log2n-io/Typhon" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; &amp;nbsp;•&amp;nbsp; 📬 &lt;a href="https://nockawa.github.io/feed.xml" rel="noopener noreferrer"&gt;Subscribe via RSS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A reader-writer lock has a lot to keep track of. How many readers are inside right now. How many are queued at the door. How many writers are queued behind &lt;em&gt;them&lt;/em&gt;. Which thread, exactly, is holding it exclusively — because releasing a lock you don't own is a bug you want to catch loudly. Whether anyone has ever had to wait on it, so you can find your hot spots later.&lt;/p&gt;

&lt;p&gt;In .NET the reflex answer is &lt;a href="https://learn.microsoft.com/dotnet/api/system.threading.readerwriterlockslim" rel="noopener noreferrer"&gt;&lt;code&gt;ReaderWriterLockSlim&lt;/code&gt;&lt;/a&gt;. It tracks all of that for you, and it costs you a heap object with a header, a sync block, an internal wait queue, and a call into the kernel when things get tense.&lt;/p&gt;

&lt;p&gt;Typhon's answer is a &lt;code&gt;ulong&lt;/code&gt;. All of it — the counter, the three classes of waiter, the owning thread id, the contention flag, the state machine — lives in &lt;strong&gt;eight bytes&lt;/strong&gt;, and every state transition is exactly one &lt;a href="https://learn.microsoft.com/dotnet/api/system.threading.interlocked.compareexchange" rel="noopener noreferrer"&gt;&lt;code&gt;CompareExchange&lt;/code&gt;&lt;/a&gt;. Uncontended, it costs &lt;strong&gt;19 nanoseconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the bit layout, the pattern that makes it readable instead of a swamp of shifts and masks, the fairness ladder that stops readers from starving writers, and an honest accounting of the parts I got wrong. And it ends on the trade that actually decides which lock goes where: every feature in that word is paid for in bits — and when the lock lives inside a page that gets written to disk, bits are a great deal more expensive than they look.&lt;/p&gt;

&lt;h2&gt;
  
  
  📍 Where we are
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://nockawa.github.io/blog/mvcc-at-microsecond-scale/" rel="noopener noreferrer"&gt;last post&lt;/a&gt; built revision chains — the per-component version lists that give every transaction its own frozen snapshot of the database. Chains get appended to while other threads are walking them, so something has to arbitrate. I waved at it and moved on: &lt;em&gt;"a small reader-writer lock in the header."&lt;/em&gt; This is that lock, and its smaller sibling.&lt;/p&gt;

&lt;p&gt;And it connects to &lt;a href="https://nockawa.github.io/blog/deadlock-free-by-construction/" rel="noopener noreferrer"&gt;Deadlock-Free by Construction&lt;/a&gt;, which made a strong claim: Typhon has no deadlock detector because it holds no &lt;em&gt;data&lt;/em&gt; locks across operations. That's still true. But "no data locks" doesn't mean "no locks" — it means the locks that remain are &lt;strong&gt;short, physical, and local&lt;/strong&gt;: a latch on a page while you read its header, a latch on a chain while you splice a revision into it. Held for tens of nanoseconds, never across a wait, never two at once in an order that could cycle. Those latches are what this post is about, and their size is the whole design constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔒 Why not just use the lock in the box?
&lt;/h2&gt;

&lt;p&gt;Steel-manning first: &lt;code&gt;ReaderWriterLockSlim&lt;/code&gt; is a good general-purpose lock, and if you need a handful of them you should use it. The trouble is what "a handful" means here.&lt;/p&gt;

&lt;p&gt;Typhon needs a latch on &lt;strong&gt;every page&lt;/strong&gt; in the page cache. Every block chain in every allocator. Every archetype cluster. Every component table. That's not a handful — in a database with a few gigabytes resident, that's &lt;em&gt;hundreds of thousands&lt;/em&gt; of locks, each one guarding a critical section that lasts about as long as a cache miss.&lt;/p&gt;

&lt;p&gt;At that multiplicity, three things about the standard answer become disqualifying:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Allocation.&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/dotnet/api/system.threading.monitor" rel="noopener noreferrer"&gt;&lt;code&gt;Monitor&lt;/code&gt;&lt;/a&gt; inflates a sync block per object; the modern &lt;a href="https://learn.microsoft.com/dotnet/api/system.threading.lock" rel="noopener noreferrer"&gt;&lt;code&gt;System.Threading.Lock&lt;/code&gt;&lt;/a&gt; that the &lt;a href="https://learn.microsoft.com/dotnet/csharp/language-reference/statements/lock" rel="noopener noreferrer"&gt;&lt;code&gt;lock&lt;/code&gt;&lt;/a&gt; statement now prefers is itself a class, and so is &lt;code&gt;ReaderWriterLockSlim&lt;/code&gt; — a heap object with a header, plus internal wait-queue machinery. Whichever you pick, you get an object. But a lock that must be a &lt;em&gt;field inside&lt;/em&gt; a page header, sitting in a memory-mapped file, cannot be an object at all. It has to be a value you can embed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Size.&lt;/strong&gt; Even if you could embed it, sixty-odd bytes of lock inside a page header you were trying to keep to a cache line is a non-starter. The lock has to be an &lt;em&gt;incidental&lt;/em&gt; cost of the structure, not a dominant one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The floor.&lt;/strong&gt; &lt;a href="https://nockawa.github.io/blog/microsecond-latency-managed-language/" rel="noopener noreferrer"&gt;Typhon's cost model&lt;/a&gt; has a hierarchy, each rung roughly ten times the last:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Thread-local read&lt;/td&gt;
&lt;td&gt;~2 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Uncontended atomic (CAS)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~5–10 ns&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Contended atomic, true sharing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20–140 ns&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;System call&lt;/td&gt;
&lt;td&gt;~500–1,000 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Context switch / blocking lock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~10,000 ns&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Thread oversubscription&lt;/td&gt;
&lt;td&gt;100,000+ ns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A lock that blocks is a lock that can cost you Level 4. Ten microseconds, to protect a critical section that was going to last fifty nanoseconds — a 200× overhead on the thing you were trying to make fast. In a database targeting microsecond transactions, that single fact drives the whole design: &lt;strong&gt;stay on rungs 1 and 2, never touch rung 4 if the work is short enough to spin through.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the requirement is a lock that is a plain value type, small enough to embed by the hundred-thousand, that transitions with atomics and never enters the kernel on the happy path. Nothing in the box does that. You have to build it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnockawa.github.io%2Fassets%2Fposts%2Flock-family-bit-layout.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnockawa.github.io%2Fassets%2Fposts%2Flock-family-bit-layout.png" alt="Bit layouts of Typhon's two reader-writer locks, drawn with field widths proportional to bit count. AccessControl fills 64 bits: shared count (bits 0-7), shared waiters (8-15), exclusive waiters (16-23), promoter waiters (24-31), exclusive thread id (32-47), a sticky contention flag (bit 48), 13 reserved bits, and a 2-bit state field (62-63). AccessControlSmall fills 32 bits — visibly half the word: shared count (bits 0-14, max 32,767), contention flag (bit 15), thread id (16-31). It has no state field, because state is implied, and no waiter counters, so it offers no fairness." width="799" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 What fits in sixty-four bits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Foundation/Concurrency/internals/AccessControl.cs" rel="noopener noreferrer"&gt;&lt;code&gt;AccessControl&lt;/code&gt;&lt;/a&gt; is one field — &lt;code&gt;private ulong _data&lt;/code&gt; — carved up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//  8  Shared usage counter   (bits 0-7)     up to 255 concurrent readers
//  8  Shared waiters         (bits 8-15)
//  8  Exclusive waiters      (bits 16-23)
//  8  Promoter waiters       (bits 24-31)
// 16  Exclusive thread id    (bits 32-47)
//  1  Contention flag        (bit 48)       sticky
// 13  Reserved               (bits 49-61)
//  2  State                  (bits 62-63)   Idle | Shared | Exclusive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design choices in there are worth pulling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three separate waiter counters, not one.&lt;/strong&gt; Readers waiting, writers waiting, and &lt;em&gt;promoters&lt;/em&gt; waiting (a promoter is a thread that already holds shared access and wants to upgrade to exclusive) each get their own byte. That looks like a luxury until you see what it buys, which is the next section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A sticky contention bit.&lt;/strong&gt; Bit 48 is set — with a single atomic OR — the first time any thread has to wait on this lock, and it's never cleared until &lt;code&gt;Reset()&lt;/code&gt;. The fast path never reads it. It costs nothing to carry, and it means that at any later moment I can sweep every page in the cache and ask &lt;em&gt;which of these locks has ever been contended, even once&lt;/em&gt; — a free, permanent, per-lock hot-spot map. It's the cheapest profiling I have anywhere in the engine: one bit, set at most once, read only by tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧵 Why the thread id is exactly sixteen bits
&lt;/h2&gt;

&lt;p&gt;The owning thread gets sixteen bits, and the field earns its keep: releasing an exclusive lock you don't own is a protocol violation, and I want it to throw rather than quietly corrupt the word. But a fixed-width thread id invites the obvious question — &lt;em&gt;what happens when you run out?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nothing, and the reason is exactly why &lt;a href="https://learn.microsoft.com/dotnet/api/system.environment.currentmanagedthreadid" rel="noopener noreferrer"&gt;&lt;code&gt;Environment.CurrentManagedThreadId&lt;/code&gt;&lt;/a&gt; exists separately from the OS thread id in the first place. The OS id is a sparse, opaque handle whose reuse is nobody's contract. The managed id is deliberately the opposite: the runtime hands out &lt;strong&gt;the lowest available number&lt;/strong&gt;, and a dead thread's id goes straight back into the pool. Ids stay dense and small — they count the threads alive &lt;em&gt;right now&lt;/em&gt;, not the threads that have ever existed.&lt;/p&gt;

&lt;p&gt;That inverts the constraint, and it's the whole reason sixteen bits is enough. Monotonic ids would make the field a &lt;em&gt;lifetime budget&lt;/em&gt;: hand out 65,535 of them over the life of the process and the next thread wraps into someone else's identity. Recycled ids make it a &lt;em&gt;concurrency ceiling&lt;/em&gt; instead — &lt;strong&gt;65,535 threads alive at the same instant.&lt;/strong&gt; A server that churns through millions of threads over a week never moves that ceiling; it just keeps handing out the same low numbers. And 65,535 simultaneously-live threads inside an embedded database engine isn't a limit to design around, it's a diagnosis: if you're there, the lock word is the least of your problems.&lt;/p&gt;

&lt;p&gt;(It started at ten bits, which was a mistake — 1,024 is fine on a laptop and nonsense on a 512-core server.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;The dependency I'm taking on, stated out loud.&lt;/strong&gt; Lowest-available reuse is what the open-source runtime &lt;em&gt;does&lt;/em&gt;; it is not what the runtime &lt;em&gt;promises&lt;/em&gt;. No specification obliges &lt;code&gt;ManagedThreadId&lt;/code&gt; to stay dense. I'm relying on an implementation detail, on purpose, and the reason I sleep at night is that the property is load-bearing for everybody who indexes anything by thread id — which is a lot of code, including the runtime's own. The incentive to preserve it is enormous and the incentive to break it is zero. But an assumption you've reasoned about and written down is a design decision; the same assumption left implicit is a landmine. So: written down.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  💎 The prettiest code in the engine
&lt;/h2&gt;

&lt;p&gt;Here's the problem with packing a lock into a word: the code turns into a swamp. Every operation becomes shift-mask-or-shift-back, and one misplaced &lt;code&gt;&amp;amp;&lt;/code&gt; is a corruption bug that shows up as a hang under load three weeks later.&lt;/p&gt;

&lt;p&gt;The fix is &lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Foundation/Concurrency/internals/AccessControl.LockData.cs" rel="noopener noreferrer"&gt;&lt;code&gt;LockData&lt;/code&gt;&lt;/a&gt; — a &lt;a href="https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/ref-struct" rel="noopener noreferrer"&gt;&lt;code&gt;ref struct&lt;/code&gt;&lt;/a&gt; that makes the word behave like an object while still committing like a word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;LockData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// points at the real lock word, in place&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;_initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// what we read&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;_staging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// what we're building&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SharedCounter&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SharedCounterMask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="n"&gt;SharedCounterMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;StateMask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="n"&gt;StateMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... SharedWaiters, ExclusiveWaiters, PromoterWaiters, ThreadId&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;TryUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;succeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareExchange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_staging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;_initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;Fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// someone beat us — re-read and let the caller retry&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the actual lock logic reads like a state machine, because it &lt;em&gt;is&lt;/em&gt; one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;IdleState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CanShareStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SharedState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;ld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SharedCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// fall through to TryUpdate()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two fields assigned, and the whole thing publishes atomically. That's the trick, and it's the part I'd take to any other system: &lt;strong&gt;&lt;code&gt;_staging&lt;/code&gt; is a local&lt;/strong&gt;. Every one of those property setters is bit arithmetic on a value sitting in a register — no atomics, no memory barriers, no contention, because nobody else can see it. You mutate it as many times as you like, as legibly as you like, and pay for exactly one atomic operation at the end when &lt;code&gt;TryUpdate&lt;/code&gt; publishes the finished word with a single &lt;code&gt;CompareExchange&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A transition that touches four logical fields costs &lt;em&gt;one&lt;/em&gt; CAS. Not four. And if it loses the race, &lt;code&gt;Fetch()&lt;/code&gt; re-reads the word and the loop reassesses from the new reality — which is why every one of these operations is written as &lt;code&gt;while (true)&lt;/code&gt; around a switch. Optimistic, retry-on-collision, no lock to take a lock.&lt;/p&gt;

&lt;p&gt;The same file has one more trick worth stealing. Blocking calls take a &lt;code&gt;ref&lt;/code&gt;&lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Foundation/Concurrency/public/WaitContext.cs" rel="noopener noreferrer"&gt;&lt;code&gt;WaitContext&lt;/code&gt;&lt;/a&gt; carrying a deadline and a cancellation token — but most callers want to wait forever and shouldn't pay for the privilege. So they pass &lt;code&gt;ref WaitContext.Null&lt;/code&gt;, and the struct checks once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_isNullRef&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ShouldStop&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;_isNullRef&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShouldStop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.unsafe.isnullref" rel="noopener noreferrer"&gt;&lt;code&gt;Unsafe.IsNullRef&lt;/code&gt;&lt;/a&gt; gives you a null &lt;em&gt;reference&lt;/em&gt; rather than a nullable &lt;em&gt;value&lt;/em&gt;: infinite wait costs one bool test, and the timeout machinery is genuinely absent rather than merely unused.&lt;/p&gt;

&lt;h2&gt;
  
  
  🪜 The fairness ladder
&lt;/h2&gt;

&lt;p&gt;Now the payoff for those three waiter counters. The entire fairness policy of the lock is three one-line predicates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;CanShareStart&lt;/span&gt;        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PromoterWaitersMask&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ExclusiveWaitersMask&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;CanExclusiveStart&lt;/span&gt;    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_staging&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;PromoterWaitersMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;CanPromoteToExclusive&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_initial&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SharedCounterMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read them as a priority ladder — &lt;strong&gt;promoters beat writers beat readers&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;new reader&lt;/strong&gt; may only enter if no writer &lt;em&gt;and&lt;/em&gt; no promoter is waiting. This is the anti-starvation rule. Under a heavy read load, a reader-preferring lock lets an endless stream of readers hold the door open forever and a writer waits until the workload changes. (This is the documented default behaviour of &lt;code&gt;ReaderWriterLockSlim&lt;/code&gt;.) Here, the instant one writer registers, the reader gate slams: incoming readers queue behind it, the in-flight readers drain, the writer goes.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;new writer&lt;/strong&gt; stands down for promoters, because a promoter is a thread already holding shared access. Making it wait behind a fresh writer would be perverse — it's already inside.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;promoter&lt;/strong&gt; may only upgrade when it is the &lt;em&gt;sole&lt;/em&gt; reader (&lt;code&gt;counter == 1&lt;/code&gt;). Two threads both holding shared access and both trying to upgrade is the textbook promotion deadlock; the counter makes it structurally unrepresentable, because at most one of them can ever see a count of one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three masks, one comparison each. No queue, no condition variable, no priority-inheritance logic — a complete fairness policy expressed as &lt;em&gt;"are these bytes zero."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🤏 Four bytes, and what they cost you
&lt;/h2&gt;

&lt;p&gt;Everything above — the waiter counters, the fairness ladder, the explicit state field — is &lt;em&gt;features&lt;/em&gt;, and every feature in that word is paid for in bits. So there's a second lock, &lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Foundation/Concurrency/internals/AccessControlSmall.cs" rel="noopener noreferrer"&gt;&lt;code&gt;AccessControlSmall&lt;/code&gt;&lt;/a&gt;, which spends half as many:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 15  Shared counter   (bits 0-14, max 32,767)
//  1  Contention flag  (bit 15)
// 16  Thread id        (bits 16-31)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing. &lt;strong&gt;There is no state field.&lt;/strong&gt; State is &lt;em&gt;implied&lt;/em&gt; — a non-zero thread id means Exclusive, a non-zero counter means Shared, both zero means Idle. Which has a lovely consequence: the Idle → Exclusive transition, the most common one there is, becomes a single CAS against literal zero.&lt;/p&gt;

&lt;p&gt;Also missing: all three waiter counters. And that is the trade, stated plainly — &lt;code&gt;AccessControlSmall&lt;/code&gt; has &lt;strong&gt;no waiter tracking, therefore no fairness&lt;/strong&gt;. A pending writer can be starved by a sustained stream of readers. Nothing clever recovers that; you either need the counters or you don't.&lt;/p&gt;

&lt;p&gt;So which lock goes where is a straight exchange of &lt;strong&gt;features against bytes&lt;/strong&gt; — and the reason bytes are worth so much more than they look is that some of these locks are not merely &lt;em&gt;in memory&lt;/em&gt;. They're in the &lt;strong&gt;data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A latch in a page header lives inside a page that gets written to disk. It is persistent state. Those four-versus-eight bytes are not a transient cost you can buy your way out of with more RAM — they are written on every flush, read back on every load, and they occupy space in the page that would otherwise be holding &lt;em&gt;your data&lt;/em&gt;, in every page, for the life of the database. Multiply by a few million pages and the difference stops being an implementation detail and starts being a storage-format decision, which is the kind of decision you don't get to revisit later.&lt;/p&gt;

&lt;p&gt;That reframes the whole question. It isn't "smaller is better" — it's that &lt;strong&gt;the byte budget of a persistent structure is nothing like the byte budget of a heap object.&lt;/strong&gt; So the rule is simply: on a structure that's central, in-memory, long-lived and genuinely contended, fairness is worth eight bytes and you take the full lock. On a structure that gets serialized into a page, you take the four-byte one, spend the savings on data, and keep the critical sections short enough that starvation never has the time to develop.&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 The numbers
&lt;/h2&gt;

&lt;p&gt;Measured with &lt;a href="https://github.com/Log2n-io/Typhon/blob/main/test/Typhon.Benchmark/AccessControlBenchmarks.cs" rel="noopener noreferrer"&gt;BenchmarkDotNet&lt;/a&gt; on an Intel Xeon Platinum 8151 @ 3.40 GHz, Ubuntu 22.04, .NET 10. Each figure is a full acquire &lt;strong&gt;and&lt;/strong&gt; release, single-threaded, with nobody to fight:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;AccessControl&lt;/code&gt; (8 B)&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;AccessControlSmall&lt;/code&gt; (4 B)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared: acquire + release&lt;/td&gt;
&lt;td&gt;18.8 ns&lt;/td&gt;
&lt;td&gt;17.0 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclusive: acquire + release&lt;/td&gt;
&lt;td&gt;21.5 ns&lt;/td&gt;
&lt;td&gt;24.5 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promote shared → exclusive → demote&lt;/td&gt;
&lt;td&gt;44 ns&lt;/td&gt;
&lt;td&gt;45.5 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the floor, and it is Level 1 of the hierarchy: a &lt;code&gt;CompareExchange&lt;/code&gt; on a line already sitting in this core's L1, with no other core wanting it.&lt;/p&gt;

&lt;p&gt;Now the opposite extreme — the same shared acquire/release, but with N−1 background threads hammering &lt;strong&gt;the same lock word&lt;/strong&gt; in a tight loop:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threads contending&lt;/th&gt;
&lt;th&gt;Shared: acquire + release&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;940 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1.86 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2.00 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Fifty to a hundredfold.&lt;/strong&gt; Nothing about the lock changed between those two tables — same word, same instructions, same code path. The only difference is that the cache line is now being dragged from core to core on every attempt.&lt;/p&gt;

&lt;p&gt;I want to be precise about what that second table &lt;em&gt;is&lt;/em&gt;, though, because it would be easy to read it as a warning about Typhon and it isn't one. It's a &lt;strong&gt;hardware&lt;/strong&gt; number, not an engine number. It's what a contended CAS costs, obtained by constructing the worst case deliberately: many threads, one word, no work in between. And the engine is built so that scenario doesn't arise.&lt;/p&gt;

&lt;p&gt;There is no central lock in Typhon — no handful of monitors that every operation funnels through. There are &lt;em&gt;hundreds of thousands&lt;/em&gt; of latches: one per page, per component table, per cluster, per block chain, each held for the tens of nanoseconds it takes to read a header or splice a pointer. For that table to describe the engine, a crowd of threads would have to want &lt;strong&gt;the same&lt;/strong&gt; latch at &lt;strong&gt;the same&lt;/strong&gt; instant. At this granularity, with hold times this short, that is close to a non-event. Not impossible — "almost never" isn't "never" — but it is nowhere near the steady state the benchmark manufactures.&lt;/p&gt;

&lt;p&gt;So read that second table as a receipt rather than a warning: it's the bill you'd be handed for centralising your locking, and it's why Typhon doesn't. &lt;strong&gt;The answer to contention was never a cleverer lock — it's granularity. Don't make one lock hot; make a hundred thousand cold ones.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚖️ Trade-offs, and what I got wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I nearly went hunting for three nanoseconds that were never there.&lt;/strong&gt; The 4-byte lock measures &lt;em&gt;slower&lt;/em&gt; at exclusive acquisition than the 8-byte one — 24.5 ns against 21.5 — which is the opposite of what its simpler fast path predicts, and I spent a while looking for the flaw in the compact design. There is no flaw. Every field read, every mask, every shift in both locks is register arithmetic, and register arithmetic is free: &lt;strong&gt;the entire price of the operation is the &lt;code&gt;Interlocked&lt;/code&gt;.&lt;/strong&gt; And a CAS has no fixed price, only a price &lt;em&gt;in a context&lt;/em&gt; — the memory system decides what the line costs, not your bit fields. My bet is that the smaller struct is likelier to share its line with whatever sits beside it, so its CAS pays coherence traffic the other's doesn't. I have no proof and won't pretend otherwise. But a three-nanosecond spread between two locks was never going to be a fact &lt;em&gt;about the locks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I shrink the lock to 4 bytes and then pad it back to 64.&lt;/strong&gt; In &lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Ecs/internals/ArchetypeClusterState.cs" rel="noopener noreferrer"&gt;&lt;code&gt;ArchetypeClusterState&lt;/code&gt;&lt;/a&gt; the latch is wrapped in a &lt;a href="https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.structlayoutattribute" rel="noopener noreferrer"&gt;&lt;code&gt;[StructLayout(Size = 64)]&lt;/code&gt;&lt;/a&gt; struct so it owns an entire cache line — it sits beside hot mutable counters, and &lt;em&gt;false sharing on a lock word is worse than a big lock word&lt;/em&gt;. Two threads taking two logically independent locks that happen to share a line will ping-pong it between cores on every CAS, at full contended cost, having never contended for anything; the engine's spatial rules go so far as to &lt;strong&gt;forbid&lt;/strong&gt; bit-packed latch arrays for exactly that reason. So the honest summary of the size story is not "smaller is better." It's: &lt;strong&gt;make the lock small enough to live where the data lives, then spend whatever padding it takes to keep its line to itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Zero-cost telemetry" is very nearly true, and I should stop saying "zero."&lt;/strong&gt; Every acquire and release has a trace-event emit call, gated on a &lt;a href="https://github.com/Log2n-io/Typhon/blob/main/src/Typhon.Engine/Observability/public/TelemetryConfig.cs" rel="noopener noreferrer"&gt;&lt;code&gt;static readonly bool&lt;/code&gt;&lt;/a&gt; so the JIT can prove the branch dead and delete it. It works — but the off-path benchmark still shows roughly 2 ns per iteration surviving versus an empty method. Two nanoseconds on a 19 ns operation is about 10%, and calling that "free" would be exactly the kind of claim I'd disbelieve in someone else's post.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 The takeaway
&lt;/h2&gt;

&lt;p&gt;The reusable idea isn't the bit layout — yours will be different. It's &lt;strong&gt;the staging pattern&lt;/strong&gt;: read the whole word once, mutate a &lt;em&gt;local copy&lt;/em&gt; as many times and as legibly as you want, publish it with one &lt;code&gt;CompareExchange&lt;/code&gt;, and retry from the top if you lost.&lt;/p&gt;

&lt;p&gt;That structure is what lets you have both of the things people assume are in tension. The lock's state can be arbitrarily rich — five fields, a state machine, a fairness ladder, a diagnostic bit — because manipulating it is just arithmetic on a register that no other thread can see. And it can still be atomic, because "atomic" is a property of the single instruction that publishes the result, not of the reasoning that produced it.&lt;/p&gt;

&lt;p&gt;Which leaves exactly one thing that costs anything: the publish. Not the shifts, not the masks, not how many fields you touched on the way — one &lt;code&gt;CompareExchange&lt;/code&gt;, and a price set entirely by the state of the cache line it lands on. So the design lever that matters was never &lt;em&gt;what you put in the word&lt;/em&gt;. It's &lt;strong&gt;where the word lives&lt;/strong&gt;. Pack it as densely and as cleverly as you like; then go make sure nothing else is touching its line.&lt;/p&gt;

&lt;p&gt;Whenever you find yourself reaching for a lock to protect a small piece of state, ask whether the state could &lt;em&gt;be&lt;/em&gt; the word instead. Often it fits. And when it fits, the lock stops being an object you acquire and becomes a number you agree on.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ What's next
&lt;/h2&gt;

&lt;p&gt;This whole post has been about making a latch cost as little as it possibly can. The next one is about the cheaper move — &lt;strong&gt;not having the structure at all.&lt;/strong&gt; Typhon used to carry a primary-key B+Tree, for the very best of reasons: every database has a primary index. That's simply what one does. It turned out to be a reflex imported from a world this engine doesn't live in, because an ECS entity is &lt;em&gt;found&lt;/em&gt; by its archetype and its key — it was never going to be walked to through an index. So the whole thing came out. &lt;strong&gt;Post #9: Why We Eliminated the Primary Key B+Tree&lt;/strong&gt; — what it cost to keep, what replaced it, and how to spot the moment you've inherited an assumption instead of making a decision.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Follow the &lt;a href="https://github.com/Log2n-io/Typhon" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; for source and benchmarks, or subscribe via &lt;a href="https://nockawa.github.io/feed.xml" rel="noopener noreferrer"&gt;RSS&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>concurrency</category>
      <category>database</category>
    </item>
    <item>
      <title>The Amazon SQS + SNS connector in redb.Route, at-least-once and pub/sub via SNS SQS. Still leaving MassTransit</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:24:18 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/the-amazon-sqs-sns-connector-in-redbroute-at-least-once-and-pubsub-via-sns-sqs-still-leaving-48dc</link>
      <guid>https://dev.to/rinat_kozin/the-amazon-sqs-sns-connector-in-redbroute-at-least-once-and-pubsub-via-sns-sqs-still-leaving-48dc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ucok93f7rbfvfab7r6x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ucok93f7rbfvfab7r6x.jpg" alt="redb.Route.SQS" width="784" height="1168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;series: "redb.Route"&lt;/p&gt;

&lt;p&gt;We've done Kafka and RabbitMQ earlier in this series. Next up: &lt;strong&gt;Amazon SQS&lt;/strong&gt; — and riding along in the same package, &lt;strong&gt;SNS&lt;/strong&gt;. Two transports, because in AWS-land they travel together. The &lt;code&gt;redb.Route.Sqs&lt;/code&gt; connector sits on the native &lt;strong&gt;AWS SDK for .NET v4&lt;/strong&gt; (&lt;code&gt;AWSSDK.SQS&lt;/code&gt;, &lt;code&gt;AWSSDK.SimpleNotificationService&lt;/code&gt;), but you don't write a "client" — you write routes, and the whole queue collapses into a single URI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders?waitTimeSeconds=20&amp;amp;concurrentConsumers=4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order in: ${body}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://process"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it once and the whole story's there: long-poll the &lt;code&gt;orders&lt;/code&gt; queue in 20-second pulls, four consumers wide, log it, pass it on. No "new up a client," no &lt;code&gt;ReceiveMessage&lt;/code&gt;, no "remember to &lt;code&gt;DeleteMessage&lt;/code&gt; after you've handled it" — the connector does all of that.&lt;/p&gt;

&lt;p&gt;About "leaving MassTransit," same disclaimer as last time: it's not that MassTransit can't do this. It's had SQS/SNS for years — &lt;a href="https://www.nuget.org/packages/MassTransit.AmazonSQS" rel="noopener noreferrer"&gt;&lt;code&gt;MassTransit.AmazonSQS&lt;/code&gt;&lt;/a&gt; — and it wires up the same SNS→SQS pairing. The difference is the &lt;strong&gt;model&lt;/strong&gt;. MassTransit is a bus — message contracts, consumers, bus config, the whole worldview bought at once. redb.Route is explicit routes in the Apache Camel spirit: an endpoint is a URI, the integration patterns are steps in a route, the transport is an abstraction underneath. This post is about what SQS and SNS look like in that second model.&lt;/p&gt;

&lt;p&gt;And the thing everyone's actually asking about right now — worth stating precisely rather than gleefully. &lt;strong&gt;MassTransit went commercial with v9&lt;/strong&gt;: the line now ships under &lt;a href="https://massient.com/" rel="noopener noreferrer"&gt;Massient, Inc.&lt;/a&gt;, and &lt;code&gt;MassTransit.AmazonSQS&lt;/code&gt; 9.0.0 on NuGet is already from there. But no spin: &lt;strong&gt;v8 stays Apache 2.0&lt;/strong&gt;, with security patches and critical fixes through at least the end of 2026; v9 isn't a closed binary — it's &lt;strong&gt;source-available&lt;/strong&gt;; and organizations under $1M USD annual revenue (plus non-profits under $1M in expenses) get it at a &lt;strong&gt;100% discount&lt;/strong&gt; — free, minus commercial support. So "MassTransit is paid now" is false for a small team and true for a large one, where it turns into a real decision: pay, ride v8 to the end of its support, or look around. &lt;code&gt;redb.Route&lt;/code&gt; is Apache 2.0, this SQS connector included — but pick it because the routing model fits your head, not because of a license line. A license is a reason to look around; it isn't an argument about architecture.&lt;/p&gt;

&lt;p&gt;All the code is in English; the examples aren't invented — they run against &lt;strong&gt;LocalStack&lt;/strong&gt; (a Docker container on &lt;code&gt;http://localhost:4566&lt;/code&gt;), the same way the connector's integration tests do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part of the redb / redb.Route series&lt;/strong&gt; — recent posts first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The RabbitMQ connector — the whole broker in one URI: RPC, competing consumers, dead-lettering &lt;em&gt;(previous in the series)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin/leaving-masstransit-for-a-camel-state-of-mind-the-kafka-connector-scatter-gather-and-what-really-106h"&gt;Leaving MassTransit for a Camel state of mind: the Kafka connector, Scatter-Gather, and transactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin/a-homegrown-apache-camel-for-net-dissected-the-http-connector-with-no-aspnet-mvc-the-56bd"&gt;Apache Camel for .NET, dissected: the HTTP connector with no ASP.NET MVC + the Content-Based Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET: 22 transports, 30+ EIP patterns, compiled DSL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sources: &lt;a href="https://github.com/redbase-app/" rel="noopener noreferrer"&gt;github.com/redbase-app&lt;/a&gt;. About the database itself: &lt;a href="https://redbase.app/" rel="noopener noreferrer"&gt;redbase.app&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  One package, two transports
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;redb.Route.Sqs&lt;/code&gt; registers &lt;strong&gt;two schemes&lt;/strong&gt;, and they don't play the same role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sqs://&lt;/code&gt;&lt;/strong&gt; — a queue. Works as both a consumer (in &lt;code&gt;From(...)&lt;/code&gt;) and a producer (in &lt;code&gt;.To(...)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sns://&lt;/code&gt;&lt;/strong&gt; — a topic. &lt;strong&gt;Publisher-only.&lt;/strong&gt; SNS has no pull consumer — delivery is push (to SQS / HTTP / email / SMS). Try to &lt;code&gt;From("sns://...")&lt;/code&gt; and it throws &lt;code&gt;NotSupportedException&lt;/code&gt; on purpose: to "read" a topic, you subscribe an SQS queue to it and read &lt;em&gt;that&lt;/em&gt; with &lt;code&gt;sqs://&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why they share a package: at AWS they're a pair. SQS is a durable point-to-point queue (one message, one consumer). SNS is a publish-subscribe topic (one message, every subscriber). The canonical AWS fan-out is an SNS topic with several SQS queues hanging off it — so one connector covers both: &lt;code&gt;sqs://&lt;/code&gt; and &lt;code&gt;sns://&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wiring it into DI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteSqs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// registers both sqs:// and sns://&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRouteBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyRoutes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Anatomy of the URI
&lt;/h2&gt;

&lt;p&gt;An endpoint is a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqs://queue-name?region=us-east-1&amp;amp;waitTimeSeconds=20&amp;amp;concurrentConsumers=4
sns://topic-name?region=us-east-1&amp;amp;autoCreateTopic=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scheme, the queue or topic name in the path, then query parameters (names are in the tables below). The fluent builder gives you the same thing: &lt;code&gt;Sqs.Queue("orders").WaitTimeSeconds(20)...&lt;/code&gt; and &lt;code&gt;Sns.Topic("events").Region("us-east-1")...&lt;/code&gt; build that exact string, URL-encoding included.&lt;/p&gt;

&lt;p&gt;Three things are AWS-specific and worth a pause:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't repeat your credentials.&lt;/strong&gt; Twenty queues on one account? Register an &lt;code&gt;AwsConnectionFactory&lt;/code&gt; in the registry and point at it with &lt;code&gt;connectionFactory=name&lt;/code&gt;. One factory builds &lt;strong&gt;both&lt;/strong&gt; the SQS and the SNS client — they share credentials, region and service URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prod-aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AwsConnectionFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"eu-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UseDefaultCredentialsProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// IAM role on the instance / SSO / env vars&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders?connectionFactory=prod-aws&amp;amp;concurrentConsumers=4"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sns://events?connectionFactory=prod-aws"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;LocalStack / ElasticMQ via &lt;code&gt;serviceUrl=&lt;/code&gt;.&lt;/strong&gt; An explicit URL overrides the regional endpoint (the region sticks around only to sign the request). That's your local-dev and test mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqs://orders?serviceUrl=http://localhost:4566&amp;amp;region=us-east-1&amp;amp;accessKey=test&amp;amp;secretKey=test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Queues and topics get created on the spot.&lt;/strong&gt; &lt;code&gt;autoCreateQueue=true&lt;/code&gt; (or &lt;code&gt;autoCreateTopic=true&lt;/code&gt;) creates the resource on startup if it's missing. A name ending in &lt;code&gt;.fifo&lt;/code&gt; comes up as a &lt;strong&gt;FIFO&lt;/strong&gt; resource automatically, with &lt;code&gt;ContentBasedDeduplication=true&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every parameter
&lt;/h2&gt;

&lt;p&gt;The reason to bookmark this post. Names are exactly as they appear in the URI; defaults are what you get out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection and credentials (shared by &lt;code&gt;sqs://&lt;/code&gt; and &lt;code&gt;sns://&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;region&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us-east-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AWS region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serviceUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Explicit endpoint (LocalStack/ElasticMQ); overrides the region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;accessKey&lt;/code&gt; / &lt;code&gt;secretKey&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Static keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessionToken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Temporary (STS) credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;profileName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;A named profile from the shared credentials file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useDefaultCredentialsProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The default AWS chain (env / IAM role / SSO)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectionFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;A factory in the registry; overrides URI credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retryCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SDK retries on transient errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectionTimeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP client timeout, ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;proxyHost&lt;/code&gt; / &lt;code&gt;proxyPort&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;HTTP proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Resolution order: the default chain → a profile → a session token → static keys. &lt;code&gt;Validate()&lt;/code&gt; insists on at least one path — &lt;code&gt;accessKey&lt;/code&gt;+&lt;code&gt;secretKey&lt;/code&gt;, &lt;code&gt;profileName&lt;/code&gt;, &lt;code&gt;useDefaultCredentialsProvider=true&lt;/code&gt;, or a registered &lt;code&gt;connectionFactory&lt;/code&gt; — otherwise it throws at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQS — consumer (&lt;code&gt;From("sqs://...")&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;waitTimeSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Long-poll (0–20). Fewer empty receives, fewer API calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxNumberOfMessages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many to pull per receive (1–10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visibilityTimeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (queue default)&lt;/td&gt;
&lt;td&gt;How long a message stays hidden while you work it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concurrentConsumers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of competing receive loops (see the concurrency recipe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;extendMessageVisibility&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keep extending visibility while the handler runs (a heartbeat); needs &lt;code&gt;visibilityTimeout &amp;gt; 0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deleteAfterRead&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete the message after a clean pass (this is the ack)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resetVisibilityOnFailure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;On error, reset visibility to 0 → immediate redelivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transacted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defer the delete into the route transaction (&lt;code&gt;.Transacted()&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;attributeNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;All&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which system attributes to request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messageAttributeNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;All&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which message attributes to request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pause (ms) after an empty receive before polling again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;initialDelay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delay (ms) before the first poll&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SQS — producer (&lt;code&gt;.To("sqs://...")&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delaySeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery delay (0–900). FIFO queues reject a per-message delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messageGroupId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;FIFO group id. A constant or a &lt;code&gt;${...}&lt;/code&gt; expression (per message)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messageDeduplicationId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;FIFO dedup id. Skip it if the queue has content-based dedup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enableBatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send an &lt;code&gt;IEnumerable&lt;/code&gt; body as one &lt;code&gt;SendMessageBatch&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batchMaxMessages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Batch size (SQS's hard cap is 10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoCreateQueue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create the queue on startup (FIFO if the name ends in &lt;code&gt;.fifo&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;queueUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;An explicit queue URL instead of resolving by name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SNS — publisher (&lt;code&gt;.To("sns://...")&lt;/code&gt;)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoCreateTopic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create the topic on startup (FIFO on &lt;code&gt;.fifo&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;topicArn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;An explicit topic ARN instead of resolving by name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subject&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Subject (for email delivery). Supports &lt;code&gt;${...}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messageStructure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;json&lt;/code&gt; — per-protocol payloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;messageGroupId&lt;/code&gt; / &lt;code&gt;messageDeduplicationId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;For FIFO topics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;subscribeSnsToSqs&lt;/code&gt; + &lt;code&gt;subscribeQueueArn&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;On startup, subscribe an SQS queue (by ARN) to this topic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rawMessageDelivery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;On that subscription, deliver the bare payload (see the pub/sub recipe)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Headers and tracing through the broker
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;sqs://&lt;/code&gt; consumer stamps message metadata onto the headers (prefixed &lt;code&gt;redbSqs.&lt;/code&gt;), and incoming &lt;strong&gt;message attributes&lt;/strong&gt; land under &lt;code&gt;redbSqs.attr.&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;What's in it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.queue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The queue name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.messageId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Message id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.receiptHandle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Receipt handle (needed to delete or change visibility)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.approximateReceiveCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many times this message has been delivered ("is this a retry?")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;redbSqs.messageGroupId&lt;/code&gt; / &lt;code&gt;redbSqs.sequenceNumber&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;FIFO group + sequence number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.sentTimestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When it was sent (epoch millis)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redbSqs.attr.&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An incoming user message attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Going the other way, the &lt;strong&gt;header → message-attribute&lt;/strong&gt; mapping on the producer goes like this: user headers ride out as attributes as-is; an incoming &lt;code&gt;redbSqs.attr.&amp;lt;name&amp;gt;&lt;/code&gt; is forwarded under the bare &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; (the prefix is stripped, so sqs→sqs and sqs→sns bridges keep their attributes); the internal &lt;code&gt;redbSqs.*&lt;/code&gt; / &lt;code&gt;redbSns.*&lt;/code&gt; keys are dropped, so one hop's metadata doesn't leak into the next.&lt;/p&gt;

&lt;p&gt;Tracing comes for free: before sending, the producer injects the W3C &lt;code&gt;traceparent&lt;/code&gt;/&lt;code&gt;tracestate&lt;/code&gt; as message attributes via the standard &lt;code&gt;DistributedContextPropagator&lt;/code&gt;, and the consumer on the far end picks them up and continues the trace. Over plain SQS (&lt;code&gt;sqs://&lt;/code&gt; → &lt;code&gt;sqs://&lt;/code&gt;) the chain is unbroken with zero setup. (There's a wrinkle when SNS is in the middle — see the pub/sub recipe.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Straight talk on delivery: at-least-once, visibility, transacted
&lt;/h2&gt;

&lt;p&gt;This is where the marketing likes to say "exactly-once." Let's read the code instead.&lt;/p&gt;

&lt;p&gt;The SQS consumer is &lt;strong&gt;at-least-once&lt;/strong&gt;. A message is deleted (&lt;code&gt;DeleteMessage&lt;/code&gt;) only &lt;strong&gt;after&lt;/strong&gt; it clears the route. The settle logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clean pass + &lt;code&gt;deleteAfterRead=true&lt;/code&gt; → &lt;code&gt;DeleteMessage&lt;/code&gt; (the ack);&lt;/li&gt;
&lt;li&gt;failed + &lt;code&gt;resetVisibilityOnFailure=true&lt;/code&gt; → &lt;code&gt;ChangeMessageVisibility(0)&lt;/code&gt; → it comes right back (fast retry);&lt;/li&gt;
&lt;li&gt;failed without that flag → it simply times out on visibility and gets redelivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;visibilityTimeout&lt;/code&gt;&lt;/strong&gt; is how long the message is hidden while you work it. Long handler? There's &lt;strong&gt;&lt;code&gt;extendMessageVisibility=true&lt;/code&gt;&lt;/strong&gt;: a background heartbeat bumps the visibility every &lt;code&gt;visibilityTimeout / 2&lt;/code&gt;, so slow work never triggers a redelivery out from under you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;transacted=true&lt;/code&gt;&lt;/strong&gt; rolls the ack into the route transaction. The delete doesn't happen immediately — a deferred &lt;code&gt;SqsAckAction&lt;/code&gt; is registered, whose &lt;code&gt;Commit&lt;/code&gt; deletes the message and whose &lt;code&gt;Rollback&lt;/code&gt; resets visibility to 0 (immediate redelivery). Commit and rollback ride along with your redb work at the &lt;code&gt;.Transacted()&lt;/code&gt; boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders?transacted=true&amp;amp;visibilityTimeout=60"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;   &lt;span class="c1"&gt;// write to the DB&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// DB committed → the message is deleted; DB failed → it comes back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's &lt;strong&gt;not&lt;/strong&gt; here is cross-queue "read from A, write to B" atomicity at the broker level — SQS doesn't have it to give. Crash in the gap between "work done" and "delete," and after a restart you get a &lt;strong&gt;repeat&lt;/strong&gt; — at-least-once, not exactly-once. So make your handler idempotent; &lt;code&gt;redbSqs.approximateReceiveCount&lt;/code&gt; is there to help. The honest label is "at-least-once with a transactional ack at the route level" — no exactly-once fairy dust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ordering&lt;/strong&gt; holds only on a FIFO queue (&lt;code&gt;.fifo&lt;/code&gt;) and only with &lt;code&gt;concurrentConsumers=1&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recipe 1: producer, consumer, FIFO, batch
&lt;/h2&gt;

&lt;p&gt;The bare minimum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Consumer — long-poll, delete after success&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WaitTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandleOrder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Producer — send the body to the queue&lt;/span&gt;
&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eu-west-1"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FIFO — the name ends in &lt;code&gt;.fifo&lt;/code&gt;, and a group id is required (a constant or a per-message expression):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders.fifo"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MessageGroupId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.customerId}"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch — an &lt;code&gt;IEnumerable&lt;/code&gt; body goes out as a single &lt;code&gt;SendMessageBatch&lt;/code&gt; (in chunks of &lt;code&gt;batchMaxMessages&lt;/code&gt;, SQS's cap being 10):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timer://tick?period=5000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"e"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;EnableBatch&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;   &lt;span class="c1"&gt;// one call instead of five&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Recipe 2: competing consumers (concurrency)
&lt;/h2&gt;

&lt;p&gt;One parameter — &lt;code&gt;concurrentConsumers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxNumberOfMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandleOrder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// up to 8 messages handled at once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;SQS-native&lt;/strong&gt; concurrency model: the connector spins up N independent receive loops (one task each), and each pulls and handles its own message. Setting &lt;code&gt;maxNumberOfMessages=1&lt;/code&gt; alongside it makes each of the N workers hold exactly one message at a time, so the pool saturates honestly instead of one loop grabbing a batch of 10. The default is &lt;code&gt;1&lt;/code&gt; (strictly serial). Go &lt;code&gt;N &amp;gt; 1&lt;/code&gt; and per-queue ordering is off the table, and your handler had better be thread-safe.&lt;/p&gt;

&lt;p&gt;How much this actually parallelizes shows up in the &lt;code&gt;SqsRpcDemo&lt;/code&gt; (more on it below): 12 requests, roughly 300 ms of work each, a pool of 4 — the whole batch clears in about 1.2 s instead of the ~3.6 s a serial consumer would spend, and the measured peak concurrency pins at exactly 4.&lt;/p&gt;

&lt;p&gt;And if what you want to parallelize is the &lt;em&gt;processing&lt;/em&gt; inside the route rather than the &lt;em&gt;intake&lt;/em&gt; (a serial source, heavy work), there's an orthogonal EIP step, &lt;code&gt;.Threads(N)&lt;/code&gt;. &lt;code&gt;concurrentConsumers&lt;/code&gt; scales reading off the queue; &lt;code&gt;.Threads(N)&lt;/code&gt; scales the work; the &lt;code&gt;CONCURRENCY.md&lt;/code&gt; guide walks through the difference. They compose freely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recipe 3: RPC (request/reply) — and why SQS won't hand it to you
&lt;/h2&gt;

&lt;p&gt;Here's the honest contrast with the last post. On RabbitMQ, RPC flipped on with &lt;strong&gt;one flag&lt;/strong&gt; — &lt;code&gt;replyTo=true&lt;/code&gt; on the client and &lt;strong&gt;zero&lt;/strong&gt; config on the server, because the broker does reply queues itself. &lt;strong&gt;SQS has none of that&lt;/strong&gt; — no protocol-level reply-to, no built-in waiting for an answer. RPC over SQS is something you build, using the classic correlation pattern: a &lt;strong&gt;reply queue&lt;/strong&gt;, a &lt;code&gt;correlationId&lt;/code&gt;, and matching the answer back. That's exactly what the &lt;code&gt;SqsRpcDemo&lt;/code&gt; shows.&lt;/p&gt;

&lt;p&gt;The client tags each request with two message attributes — &lt;code&gt;correlationId&lt;/code&gt; and &lt;code&gt;replyTo&lt;/code&gt; (the queue to answer on) — and drops it on the request queue. The worker computes a result and sends it to the queue named in &lt;code&gt;replyTo&lt;/code&gt;; the &lt;code&gt;correlationId&lt;/code&gt; rides back on its own (the incoming &lt;code&gt;redbSqs.attr.correlationId&lt;/code&gt; is forwarded straight back out as an attribute). The worker is a plain consumer plus a dynamic &lt;code&gt;.ToD(...)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Worker: N competing consumers on the request queue, reply to the queue named in replyTo&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rpc-requests"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxNumberOfMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()!);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// simulated work&lt;/span&gt;
        &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// the reply body&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;// dynamic destination — the reply queue comes from the request's replyTo attribute&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReplyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client side a separate route reads the reply queue and matches the &lt;code&gt;correlationId&lt;/code&gt; back to the pending call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rpc-replies"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxNumberOfMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"correlationId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryRemove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tcs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;tcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrySetResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The takeaway: RPC on SQS works, but it's &lt;strong&gt;assembled from primitives&lt;/strong&gt; (attributes + &lt;code&gt;.ToD&lt;/code&gt; + correlation), not toggled with a flag. When you want RPC out of the box, you reach for a broker with a native reply path (RabbitMQ). SQS is honest about what it is: durable queues and redelivery, and request/reply is a thing you wire up on top. The full, runnable version lives in &lt;code&gt;redb.Route/demos/SqsRpcDemo&lt;/code&gt; (it runs against LocalStack).&lt;/p&gt;




&lt;h2&gt;
  
  
  Recipe 4 (EIP): Publish-Subscribe via SNS→SQS fan-out
&lt;/h2&gt;

&lt;p&gt;Here's the headline pattern, the whole reason SQS keeps SNS company. &lt;strong&gt;Publish-Subscribe Channel&lt;/strong&gt;, straight out of Hohpe &amp;amp; Woolf: the publisher sends &lt;strong&gt;one&lt;/strong&gt; message and &lt;strong&gt;every&lt;/strong&gt; subscriber gets it — independently, into its own queue, with its own retries. At AWS you don't do this in code, you do it with topology: an SNS topic with several SQS queues subscribed to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish "order" ──▶ SNS topic ──┬──▶ SQS "orders-billing"  ──▶ billing route
                                 └──▶ SQS "orders-shipping" ──▶ shipping route
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The publisher just publishes to the topic, and every subscribed queue gets a copy — each with its own independent consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Publisher — publish an order event; SNS fans it out to every subscribed queue:&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timer://orders?period=5000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;BuildOrder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-events"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Two independent subscribers — each its own SQS queue, each its own consumer:&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-billing"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Charge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-shipping"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ship&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subscriptions themselves the connector can set up from the URI — &lt;code&gt;subscribeSnsToSqs&lt;/code&gt; + the queue ARN; the subscribe runs when the &lt;code&gt;sns://&lt;/code&gt; producer starts. Each &lt;code&gt;sns://&lt;/code&gt; endpoint subscribes &lt;strong&gt;one&lt;/strong&gt; queue (a single ARN), so a two-queue fan-out is two subscriptions on the one topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subscribe each queue to the topic with raw delivery (bare payload, no envelope):&lt;/span&gt;
&lt;span class="n"&gt;Sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-events"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SubscribeSnsToSqs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;billingArn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RawMessageDelivery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-events"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SubscribeSnsToSqs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shippingArn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RawMessageDelivery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the exact fan-out wiring (creating the queues, reading their ARNs, subscribing, publishing), see &lt;code&gt;redb.Route/demos/SqsPubSubDemo&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ The catch: the envelope vs. raw
&lt;/h3&gt;

&lt;p&gt;By default SNS does &lt;strong&gt;not&lt;/strong&gt; drop your payload into the queue as-is — it wraps it in a &lt;strong&gt;JSON notification envelope&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Notification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MessageId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"TopicArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your payload, as a string&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MessageAttributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the subscribing queue receives the &lt;strong&gt;envelope&lt;/strong&gt;, not your message, and your SNS attributes sit inside it rather than as SQS attributes. That's standard AWS SNS behavior (the subscription's &lt;strong&gt;Raw Message Delivery = OFF&lt;/strong&gt;), and it earns its keep in exactly one case: when a single queue listens to several topics and you need the envelope's &lt;code&gt;TopicArn&lt;/code&gt; to tell them apart. For an ordinary fan-out it's just in the way — you end up unwrapping the envelope, and the SNS→SQS trace breaks (the &lt;code&gt;traceparent&lt;/code&gt; goes inside the envelope, while the SQS consumer looks for it among the SQS attributes).&lt;/p&gt;

&lt;p&gt;The fix is the subscription's &lt;strong&gt;Raw Message Delivery = true&lt;/strong&gt;: the queue gets the &lt;strong&gt;bare payload&lt;/strong&gt;, and the SNS attributes become SQS attributes (and the trace is whole again). In the connector that's &lt;code&gt;.RawMessageDelivery()&lt;/code&gt; — it sets &lt;code&gt;RawMessageDelivery=true&lt;/code&gt; on that &lt;code&gt;subscribeSnsToSqs&lt;/code&gt; subscription. The &lt;code&gt;SqsPubSubDemo&lt;/code&gt; prints the first delivered body: with &lt;code&gt;.RawMessageDelivery()&lt;/code&gt; it's &lt;code&gt;{"orderId":1,"amount":100}&lt;/code&gt;, not an envelope.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On &lt;code&gt;rawMessageDelivery&lt;/code&gt;.&lt;/strong&gt; This option lands in the &lt;strong&gt;next NuGet release&lt;/strong&gt; of &lt;code&gt;redb.Route.Sqs&lt;/code&gt;. You can already grab it from source right now — &lt;a href="https://github.com/redbase-app/redb-route/tree/main/redb.Route.Sqs" rel="noopener noreferrer"&gt;the &lt;code&gt;redb.Route.Sqs&lt;/code&gt; connector on GitHub&lt;/a&gt; (the connector folder — &lt;code&gt;SnsEndpointOptions&lt;/code&gt; / &lt;code&gt;SnsProducer&lt;/code&gt; / the &lt;code&gt;RawMessageDelivery&lt;/code&gt; fluent method). Without it, SNS→SQS still works; you just get the JSON envelope and unwrap it yourself (&lt;code&gt;envelope.Message&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Recipe 5 (a cousin): Multicast — the same fan-out, but inside the route
&lt;/h2&gt;

&lt;p&gt;Publish-Subscribe copies the message &lt;strong&gt;on the broker&lt;/strong&gt; (SNS hands a copy to each subscribed queue). There's a close cousin that copies &lt;strong&gt;inside the route&lt;/strong&gt; — &lt;strong&gt;Multicast&lt;/strong&gt;: your step sends a copy of the exchange to several destinations itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders-billing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqs://orders-shipping"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks similar, but the difference is fundamental — &lt;strong&gt;where the copying happens, and who knows about whom&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SNS→SQS (pub/sub)&lt;/th&gt;
&lt;th&gt;Multicast&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where it copies&lt;/td&gt;
&lt;td&gt;on the broker (SNS)&lt;/td&gt;
&lt;td&gt;in the route (your process)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who knows the recipients&lt;/td&gt;
&lt;td&gt;SNS (the subscriptions)&lt;/td&gt;
&lt;td&gt;the route (the &lt;code&gt;.To&lt;/code&gt; list)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding a recipient&lt;/td&gt;
&lt;td&gt;subscribe another queue, leave the publisher alone&lt;/td&gt;
&lt;td&gt;edit the route's code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;fully decoupled&lt;/td&gt;
&lt;td&gt;recipients are baked into the route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallelism / aggregating replies&lt;/td&gt;
&lt;td&gt;none (fire-and-forget per subscription)&lt;/td&gt;
&lt;td&gt;yes: &lt;code&gt;.Parallel().MaxParallelism(N)&lt;/code&gt;, merge the branches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule of thumb: need to &lt;strong&gt;decouple the publisher from the subscribers&lt;/strong&gt; (anyone can subscribe later, the publisher never knows) — SNS→SQS. Need to &lt;strong&gt;fan out to a fixed list right here and maybe collect the answers&lt;/strong&gt; — Multicast (or its sibling Scatter-Gather from the Kafka post). One is broker topology, the other is a route step; you pick by where the fan-out logic should live.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;SQS and SNS in redb.Route are routes where the whole queue and the whole topic are one URI: &lt;code&gt;From("sqs://…")&lt;/code&gt; / &lt;code&gt;.To("sqs://…")&lt;/code&gt; / &lt;code&gt;.To("sns://…")&lt;/code&gt;, with all the AWS SDK plumbing, the receive/delete dance, visibility, subscriptions and correlation tucked behind the string's parameters. Long-poll, FIFO, batching, competing consumers on a single parameter, at-least-once with a transactional ack, SNS→SQS fan-out with a choice of "envelope or bare payload" — the set is complete, and what you're left with is a short, legible string.&lt;/p&gt;

&lt;p&gt;Straight talk on the trade-offs: there's no exactly-once (SQS itself doesn't offer it) — you get at-least-once plus idempotency; RPC isn't out of the box, it's assembled from attributes and &lt;code&gt;.ToD&lt;/code&gt;; and an SNS subscription wraps your payload in an envelope by default. All of that is AWS being AWS, not the connector cutting corners — and the connector is upfront about every bit of it.&lt;/p&gt;

&lt;p&gt;Two runnable examples — &lt;code&gt;redb.Route/demos/SqsRpcDemo&lt;/code&gt; (RPC + concurrency) and &lt;code&gt;redb.Route/demos/SqsPubSubDemo&lt;/code&gt; (SNS→SQS fan-out + raw delivery); both come up on LocalStack (&lt;code&gt;http://localhost:4566&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;/&lt;code&gt;test&lt;/code&gt;) and run as a self-test. If something bites in your scenario, say so in the comments.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sources and releases: &lt;a href="https://github.com/redbase-app/" rel="noopener noreferrer"&gt;github.com/redbase-app&lt;/a&gt;. About the redb database: &lt;a href="https://redbase.app/" rel="noopener noreferrer"&gt;redbase.app&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If this was useful — a ⭐ on &lt;a href="https://github.com/redbase-app/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps others find it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>aws</category>
      <category>sqs</category>
    </item>
    <item>
      <title>Dependency Management Doesn't Have to Be Complicated: A Better Approach for .NET Developers</title>
      <dc:creator>Marcom</dc:creator>
      <pubDate>Thu, 16 Jul 2026 11:16:48 +0000</pubDate>
      <link>https://dev.to/marcom/dependency-management-doesnt-have-to-be-complicated-a-better-approach-for-net-developers-2p3b</link>
      <guid>https://dev.to/marcom/dependency-management-doesnt-have-to-be-complicated-a-better-approach-for-net-developers-2p3b</guid>
      <description>&lt;p&gt;Every .NET developer has encountered dependency issues at some point.&lt;/p&gt;

&lt;p&gt;A package version conflict appears after an update. A project suddenly fails to build because of incompatible NuGet packages. Teams spend hours troubleshooting dependency chains instead of writing code.&lt;/p&gt;

&lt;p&gt;As applications grow larger and more modular, dependency management becomes increasingly difficult and increasingly important.&lt;/p&gt;

&lt;p&gt;The good news is that modern tools are making it easier to identify, analyze, and resolve these issues before they impact development.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.pal.tech/technology/simplifying-nuget-dependency-management-with-nugetsolver/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pal.tech%2Fwp-content%2Fuploads%2F2024%2F05%2FNugetIssue.jpg" height="352" class="m-0" width="799"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.pal.tech/technology/simplifying-nuget-dependency-management-with-nugetsolver/" rel="noopener noreferrer" class="c-link"&gt;
            NuGet Dependency Mgmt – PalTech USA, UK, Australia
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            PalTech simplifies NuGet Dependency Management in USA, UK &amp;amp; Australia. Optimize development workflows with consulting &amp;amp; automation services.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pal.tech%2Fwp-content%2Fuploads%2F2025%2F01%2Ffavicon.png" width="16" height="16"&gt;
          pal.tech
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why Dependency Management Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NuGet has transformed the .NET ecosystem by making it simple to reuse libraries and accelerate development.&lt;/p&gt;

&lt;p&gt;However, every additional package introduces another dependency that must be maintained over time.&lt;/p&gt;

&lt;p&gt;Poor dependency management can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version conflicts&lt;/li&gt;
&lt;li&gt;Build failures&lt;/li&gt;
&lt;li&gt;Security vulnerabilities&lt;/li&gt;
&lt;li&gt;Difficult upgrades&lt;/li&gt;
&lt;li&gt;Increased maintenance effort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;a href="https://www.pal.tech/digital-product-engineering/" rel="noopener noreferrer"&gt;enterprise applications&lt;/a&gt; with dozens or even hundreds of dependencies, manually tracking package relationships quickly becomes impractical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smarter Dependency Resolution Improves Developer Productivity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern dependency analysis tools help developers understand how packages interact across an application.&lt;/p&gt;

&lt;p&gt;Instead of manually tracing references, developers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visualize dependency trees&lt;/li&gt;
&lt;li&gt;Detect version conflicts early&lt;/li&gt;
&lt;li&gt;Identify outdated packages&lt;/li&gt;
&lt;li&gt;Simplify upgrade planning&lt;/li&gt;
&lt;li&gt;Improve application stability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is less time spent debugging package issues and more time focused on delivering business value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Is Becoming a Competitive Advantage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Organizations often focus on customer experience, but developer experience matters just as much.&lt;/p&gt;

&lt;p&gt;When engineers have reliable tooling, automated workflows, and clear visibility into application dependencies, they can develop, test, and deploy software more efficiently.&lt;/p&gt;

&lt;p&gt;Small improvements in developer productivity can have a significant impact across large engineering teams.&lt;/p&gt;

&lt;p&gt;As software ecosystems continue growing in complexity, better dependency management will become an essential part of modern software engineering.&lt;/p&gt;

&lt;p&gt;If you're interested in simplifying package management for .NET applications, &lt;a href="https://www.pal.tech/technology/simplifying-nuget-dependency-management-with-nugetsolver/" rel="noopener noreferrer"&gt;PalTech's article, Simplifying NuGet Dependency Management with NuGetSolver&lt;/a&gt;, explores how developers can identify dependency conflicts more efficiently, improve project maintainability, and streamline application development.&lt;/p&gt;

</description>
      <category>dotnet</category>
    </item>
    <item>
      <title>Modern Secrets for Legacy .NET: Building a Typed, Cached AWS Secrets Manager Library</title>
      <dc:creator>David Gates</dc:creator>
      <pubDate>Thu, 16 Jul 2026 10:53:27 +0000</pubDate>
      <link>https://dev.to/dgates82/modern-secrets-for-legacy-net-building-a-typed-cached-aws-secrets-manager-library-2mpp</link>
      <guid>https://dev.to/dgates82/modern-secrets-for-legacy-net-building-a-typed-cached-aws-secrets-manager-library-2mpp</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you're building on modern .NET, retrieving a secret from AWS Secrets Manager as a strongly typed, cached configuration value is pretty much turnkey. AWS's supporting NuGet packages handle TTL-based (time-to-live) caching out of the box, and let you bind secrets straight into &lt;code&gt;IConfiguration&lt;/code&gt; with only a few lines of code.&lt;/p&gt;

&lt;p&gt;If you're maintaining a .NET Framework 4.8 application — and there are more of these in production than any of us would like to admit — none of that tooling exists. The AWS SDK for .NET supports .NET Framework 4.8; you can certainly call &lt;code&gt;AmazonSecretsManagerClient.GetSecretValueAsync&lt;/code&gt; from a Framework app today. What doesn't exist is the supporting ecosystem that modern .NET developers take for granted: no typed binding, no caching client, and nothing comparable to &lt;code&gt;IConfiguration&lt;/code&gt;'s abstraction over configuration sources. You're left hand-rolling JSON deserialization, deciding for yourself whether and how to cache secrets, and wiring the rest together by hand.&lt;/p&gt;

&lt;p&gt;A lot of what's still running out there is classic ASP.NET MVC 5 — &lt;code&gt;System.Web.Mvc&lt;/code&gt;, &lt;code&gt;Global.asax&lt;/code&gt;-based startup, with no built-in dependency injection (DI). That's not a niche scenario; it's the reality for many enterprise applications across organizations of every size.&lt;/p&gt;

&lt;p&gt;I ran into this gap firsthand when my organization decided to standardize on AWS Secrets Manager across both modern and legacy .NET applications. The modern apps were straightforward: install the packages, bind to &lt;code&gt;IConfiguration&lt;/code&gt;, and you're done. The first .NET Framework 4.8 application was a different story. It needed the same typed retrieval, caching, and clean integration, but none of that infrastructure existed. Every legacy application in our portfolio would face the same challenge. I built &lt;code&gt;DGates.AwsSecretsManager&lt;/code&gt; to solve it once, rather than repeating the same solution across every legacy application.&lt;/p&gt;

&lt;p&gt;So the goal here wasn't to invent AWS Secrets Manager support for .NET Framework; that already existed. The goal was to close the &lt;em&gt;ergonomics&lt;/em&gt; gap: build the kind of typed, cached, DI-friendly wrapper that modern .NET developers get for free today, in a form that a legacy .NET Framework app — including one with no DI container and no modern configuration pipeline — can actually adopt without a rewrite.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager" rel="noopener noreferrer"&gt;source&lt;/a&gt;, &lt;a href="https://www.nuget.org/packages/DGates.AwsSecretsManager" rel="noopener noreferrer"&gt;NuGet package&lt;/a&gt;, and &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager.Examples" rel="noopener noreferrer"&gt;example applications&lt;/a&gt; are all available if you'd like to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Decisions &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;p&gt;This is the part of the library that matters — not "I called an AWS SDK method," but the decisions around &lt;em&gt;how&lt;/em&gt; to expose it. Every decision came back to one constraint: the library had to feel natural in a .NET Framework 4.8 application, without requiring that application to become more "modern" to use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typed retrieval, not string wrangling.&lt;/strong&gt; The library's API centers on &lt;code&gt;GetSecretAsync&amp;lt;T&amp;gt;&lt;/code&gt; — deliberately mirroring the typed configuration binding modern .NET developers already expect. Tell it what shape you expect back, and it hands you an object — not a JSON blob you deserialize yourself three call sites later. &lt;code&gt;GetSecretStringAsync&lt;/code&gt; exists too, for the cases where a secret genuinely is just a string and wrapping it in a POCO (plain old CLR object) would be overkill. &lt;code&gt;RefreshSecretAsync&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;InvalidateCache&lt;/code&gt; round out the API — force a refresh when you know a secret rotated, or drop it from cache entirely without waiting for TTL expiry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching: &lt;code&gt;ConcurrentDictionary&lt;/code&gt;, not &lt;code&gt;IMemoryCache&lt;/code&gt;.&lt;/strong&gt; This one is a deliberate departure from what a lot of people would reach for first. &lt;code&gt;Microsoft.Extensions.Caching.Memory&lt;/code&gt; is the obvious modern choice, but pulling it into a .NET Framework 4.8 library means dragging in a dependency chain built for a different runtime generation, plus the DI registration ceremony that comes with it. That's exactly the kind of friction we want to avoid with this library. A &lt;code&gt;ConcurrentDictionary&lt;/code&gt; keyed by secret name, storing the value alongside its expiry, does everything needed here: thread-safe reads and writes, no extra package, no DI requirement. &lt;code&gt;CacheTtl&lt;/code&gt; is exposed as a &lt;code&gt;TimeSpan&lt;/code&gt; rather than an int, because "how long should this live" is a duration, and forcing a caller to remember whether an int means seconds or milliseconds is the kind of API ambiguity that causes production incidents at 2 a.m.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resilience: Polly, scoped narrowly.&lt;/strong&gt; Secrets Manager calls can fail transiently, and retrying blindly can be worse than not retrying at all if you're not careful about &lt;em&gt;what&lt;/em&gt; you retry. Polly 8 wraps the AWS calls specifically for transient failure modes — throttling, temporary network failures, and similar conditions — not as a blanket "wrap everything and hope" policy. Non-transient failures, like invalid credentials or a secret that does not exist, surface immediately instead of being retried into a longer, more confusing failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DI-agnostic by design.&lt;/strong&gt; This decision came directly from the target audience. A modern ASP.NET app has a DI container built in. A classic ASP.NET MVC 5 app, running through &lt;code&gt;Global.asax&lt;/code&gt;, does not. Forcing a specific container (Autofac, Unity, whatever) onto a legacy app just to use this library would be limiting and defeat the purpose. Instead, &lt;code&gt;SecretsManagerServiceFactory&lt;/code&gt; builds a configured &lt;code&gt;SecretsManagerService&lt;/code&gt; directly, and &lt;code&gt;SecretsManagerService&lt;/code&gt; itself is directly instantiable — no container required. Consumers who &lt;em&gt;do&lt;/em&gt; have a DI container can register it as usual; consumers who don't can call the factory once at startup and hold the result in a static accessor. Both are first-class, not one blessed path and one workaround.&lt;/p&gt;

&lt;p&gt;The same restraint applies to authentication. &lt;code&gt;SecretsManagerSettings&lt;/code&gt; accepts an explicit &lt;code&gt;AccessKey&lt;/code&gt; and &lt;code&gt;SecretKey&lt;/code&gt; for the cases that genuinely need them, but when those values are omitted, the library defers to the AWS SDK's standard credential chain — environment variables, an &lt;code&gt;.aws/credentials&lt;/code&gt; profile, or an IAM role. Rather than inventing its own authentication model, the library relies on the one AWS developers already know and expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local development without AWS.&lt;/strong&gt; None of the above matters much if testing it locally means burning AWS API calls against a real Secrets Manager instance every time you hit F5, or requiring an AWS account just to try the library out. The library ships with a &lt;code&gt;docker-compose.yml&lt;/code&gt; that spins up LocalStack and a seed script that populates it with test secrets, so a fresh checkout can run the integration tests against a fully local AWS emulation with a single command: no AWS account, no manual setup. The examples repo follows the same pattern.&lt;/p&gt;

&lt;p&gt;For environments where Docker isn't available — nested VMs without virtualization passthrough are a common setup in practice — there's a &lt;code&gt;LocalJsonFallbackPath&lt;/code&gt; that reads secrets from a local JSON file instead. It's not a mock; it's a genuine fallback path, wired the same way the real client is, so switching between "talk to LocalStack," "talk to real AWS," and "read a local file" is a configuration change, not a code change. (To be clear: this fallback is a local development and testing convenience, not something intended for production use.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving It in a Real App: MvcExample
&lt;/h2&gt;

&lt;p&gt;Could every application implement this itself? Absolutely — the AWS SDK makes that possible, if not particularly pleasant. But once you're standardizing Secrets Manager across dozens of legacy applications, the problem isn't writing another JSON deserializer. It's making sure every application gets the same caching behavior, retry policy, configuration model, and local development experience — instead of a dozen slightly different, independently maintained versions of the same idea.&lt;/p&gt;

&lt;p&gt;A library intended for ASP.NET MVC 5 isn't really proven by a console sample alone. To validate &lt;code&gt;DGates.AwsSecretsManager&lt;/code&gt; in the kind of environment it was actually built for, I created a classic ASP.NET MVC 5 application — &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager.Examples/tree/main/src/MvcExample" rel="noopener noreferrer"&gt;MvcExample&lt;/a&gt; — where a user enters a city, the app retrieves an OpenWeatherMap API key from Secrets Manager, geocodes the city, and fetches live weather. A "how this worked" panel shows the secret name, the retrieved URL, and which backend served the request — real AWS, LocalStack, or the JSON fallback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89diyqr6q6tubdy1e5kw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89diyqr6q6tubdy1e5kw.png" alt="MvcExample showing weather results for a searched city, with the " width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Startup integration is a single factory call — no DI container required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Global.asax.cs — Application_Start&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecretsManagerServiceFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SecretsManagerSettings&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"us-west-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CacheTtl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;LocalJsonFallbackPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/App_Data/secrets.local.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieval is one typed call — the secret comes back as a POCO, already deserialized, already cached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSecretAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OpenWeatherMapSecret&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev/MvcExample/OpenWeatherMap"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the ergonomics gap from the start of this post, closed: typed, cached, and wired into a &lt;code&gt;Global.asax&lt;/code&gt;-era app without a rewrite.&lt;/p&gt;

&lt;p&gt;In MvcExample, both calls live behind a small static accessor class: it reads the settings from &lt;code&gt;Web.config&lt;/code&gt; instead of hardcoding them, guards initialization so the service is built exactly once, and wraps retrieval results with the metadata the "how this worked" panel displays. None of that is required by the library itself — it's the app's own glue — but the pattern is worth copying if you want environment-specific configuration without a recompile. See &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager.Examples/blob/main/src/MvcExample.Infrastructure/SecretsManagerAccessor.cs" rel="noopener noreferrer"&gt;SecretsManagerAccessor.cs&lt;/a&gt; for the full version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legacy packaging friction.&lt;/strong&gt; MVC5 projects use &lt;code&gt;packages.config&lt;/code&gt;, and &lt;code&gt;DGates.AwsSecretsManager&lt;/code&gt; brings the AWS SDK's sizeable transitive dependency tree with it — something you do not want to manage through hand-wired &lt;code&gt;HintPath&lt;/code&gt; entries. The long-term solution is to convert the legacy project's packages to &lt;code&gt;PackageReference&lt;/code&gt; and isolate all library-facing code in a small SDK-style project — in this case, &lt;code&gt;MvcExample.Infrastructure&lt;/code&gt;. The web project references that project through &lt;code&gt;ProjectReference&lt;/code&gt;, letting MSBuild resolve the full dependency graph while the legacy project never has to know the AWS SDK exists. As a bonus, the conversion also enables &lt;code&gt;dotnet restore&lt;/code&gt;, making clean command-line and CI builds possible without relying on Visual Studio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two operational lessons.&lt;/strong&gt; First, &lt;code&gt;Application_Start&lt;/code&gt; runs eagerly — so an unreachable Secrets Manager at startup meant a crashed app pool, not an error page. Initialization is now handled defensively, surfacing failures as an in-page message at first real use instead. Second, &lt;code&gt;LocalJsonFallbackPath&lt;/code&gt; isn't automatic — if you want the fallback, it has to be wired explicitly at the consumer's configuration call site. It was missing entirely from MvcExample's initial setup, a reminder that it needs to be verified in every new consumer, not assumed from the README.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the panel doesn't show.&lt;/strong&gt; The "how this worked" panel doesn't display a cache-hit indicator, and that's deliberate. Building one would have meant inferring the library's internal cache state from the outside rather than reporting something real — false precision dressed up as telemetry. Better to show nothing than something that looks like data but isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building &amp;amp; Publishing
&lt;/h2&gt;

&lt;p&gt;A .NET Framework 4.8 library in 2026 raises an immediate CI question: what does the pipeline even run on? GitHub Actions' Windows runners are the obvious answer, but LocalStack ships as a Linux container and isn't supported under Windows containers. The &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager" rel="noopener noreferrer"&gt;library&lt;/a&gt; is built and tested on &lt;code&gt;ubuntu-latest&lt;/code&gt; instead, using Mono to run the .NET Framework test suite. Linux runners also spin up faster, and keeping the pipeline on one OS keeps it simple — the same environment builds the library, runs the tests against LocalStack, and packs the NuGet package.&lt;/p&gt;

&lt;p&gt;There's one hard limit to know about: this works for &lt;em&gt;building&lt;/em&gt; .NET Framework code, not running classic ASP.NET on Linux. Compiling the MVC 5 example app on &lt;code&gt;ubuntu-latest&lt;/code&gt; works fine; hosting it does not — Mono's &lt;code&gt;xsp4&lt;/code&gt; web host is broken and unmaintained. Build on Linux, run on Windows via IIS Express. That boundary is worth knowing before you design a pipeline around either assumption.&lt;/p&gt;

&lt;p&gt;Two principles shaped the rest of the pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No NuGet API keys, anywhere.&lt;/strong&gt; Publishing uses NuGet's Trusted Publishing — the workflow authenticates via OIDC (OpenID Connect), meaning NuGet.org trusts a short-lived token issued by GitHub Actions for this specific repository and workflow, rather than a long-lived API key stored as a repository secret. There is nothing to leak, rotate, or accidentally commit. For a library whose entire purpose is keeping secrets out of the wrong places, publishing it with a static credential would have been a little too ironic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqwn5kromw4hm0jnbowm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqwn5kromw4hm0jnbowm.png" alt="DGates.AwsSecretsManager listed on NuGet.org, showing the prerelease version, install command, and download stats" width="799" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests run against real (local) infrastructure.&lt;/strong&gt; The same &lt;code&gt;docker-compose.yml&lt;/code&gt; and seed script that power local development spin up LocalStack in CI, so the integration tests exercise genuine Secrets Manager API behavior on every push — not mocks. The local-dev investment from earlier paid for itself here: CI didn't need its own separate test infrastructure, because the local story already was one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Three things stand out looking back at this project — none of them about AWS or Secrets Manager specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local development isn't an afterthought.&lt;/strong&gt; The LocalStack setup and the JSON fallback path took real time to build — time that could have gone toward features. It was worth it twice over. During development, the zero-dependency local story meant every design decision could be exercised immediately, in environments ranging from a full Docker setup to a locked-down VM with nothing on it. Then it paid off again in CI: the same &lt;code&gt;docker-compose.yml&lt;/code&gt; and seed script that power local development became the integration test infrastructure, with no separate CI-only setup to build or maintain. Investment in the development experience compounds; it isn't overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for the audience you actually have.&lt;/strong&gt; It would have been easier to build this with the newest patterns available: a modern DI container, &lt;code&gt;IOptions&lt;/code&gt;-style configuration, the latest caching abstractions. The applications that need this library can't use any of that. Every design decision in this post traces back to accepting that constraint instead of fighting it: the DI-agnostic factory exists because the target apps have no container, converting package management is a much smaller step than rewriting the project around an SDK-style project model, and configuration flows through &lt;code&gt;Web.config&lt;/code&gt; because that's where these apps keep it. Meeting legacy applications where they are is what makes incremental modernization possible at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency scales; convenience doesn't.&lt;/strong&gt; Any individual application could hand-roll its own Secrets Manager integration in an afternoon. Taken once, that's perfectly reasonable. Taken a dozen times across a portfolio, it produces a dozen slightly different caching policies, retry behaviors, and configuration conventions, each independently maintained, each evolving in slightly different directions. A shared library means one implementation to test, one set of behaviors to reason about, and one place to fix a bug that would otherwise be a dozen tickets. The value isn't that it saved anyone from writing a JSON deserializer — it's that nobody has to wonder which version of the pattern any given app is running.&lt;/p&gt;

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

&lt;p&gt;The surprising part of this project wasn't the implementation itself. It was realizing that the hardest problems weren't making it work — they were making it fit. Legacy applications aren't difficult because they're old; they're difficult because they're valuable enough to keep running while the world around them changes.&lt;/p&gt;

&lt;p&gt;That's ultimately what &lt;code&gt;DGates.AwsSecretsManager&lt;/code&gt; aims to provide: not new capabilities, but modern ergonomics for software that's still doing real work.&lt;/p&gt;

&lt;p&gt;If this library helps even a handful of teams modernize legacy applications incrementally instead of rewriting them all at once, it will have done exactly what it was built to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The library is currently in beta. Before a stable 1.0 release, I want to focus on two areas: structured logging and deeper documentation of the design decisions covered here. Beyond that are ideas like cache stampede protection, serializer abstraction, and rotation handling — but roadmaps change, so the &lt;a href="https://github.com/dgates82/DGates.AwsSecretsManager/issues" rel="noopener noreferrer"&gt;issue tracker&lt;/a&gt; remains the source of truth for what's actually planned.&lt;/p&gt;

&lt;p&gt;If you're maintaining .NET Framework applications and this problem looks familiar, I'd love to hear how you're solving it — or whether &lt;a href="https://www.nuget.org/packages/DGates.AwsSecretsManager" rel="noopener noreferrer"&gt;&lt;code&gt;DGates.AwsSecretsManager&lt;/code&gt;&lt;/a&gt; helps. Feedback from real legacy codebases is worth more than any roadmap.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aws</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Building a Zero-Hardware Keyboard Light: My Journey with C#, WPF, and OLED Efficiency</title>
      <dc:creator>Mihai Pavelescu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:27:10 +0000</pubDate>
      <link>https://dev.to/mediaexpres/building-a-zero-hardware-keyboard-light-my-journey-with-c-wpf-and-oled-efficiency-3iak</link>
      <guid>https://dev.to/mediaexpres/building-a-zero-hardware-keyboard-light-my-journey-with-c-wpf-and-oled-efficiency-3iak</guid>
      <description>&lt;p&gt;Working late nights on server migrations and code architectures often means typing in low-light environments. While USB lamps or backlit keyboards are the standard solutions, they consume extra power and add physical clutter. I realized the ultimate light source was already directly in front of me: the monitor. &lt;/p&gt;

&lt;p&gt;With a clear vision in mind, I partnered with Google's Gemini AI to rapidly prototype and refine what became &lt;strong&gt;LightBar For Keyboard&lt;/strong&gt;, a lightweight Windows application that creates a reflective light bar at the bottom of the screen to illuminate the keys. Here is how we built it using C# and WPF, tackled the Windows API to manage screen space, and optimized it for modern OLED energy consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Challenge: Desktop Toolbars (AppBar)
&lt;/h2&gt;

&lt;p&gt;The simplest approach to creating a light bar is a borderless, top-most window. However, the immediate UX flaw is that maximized applications (like Chrome or Visual Studio) will either cover the bar or be partially obscured by it. &lt;/p&gt;

&lt;p&gt;To solve this, the application needed to behave like the Windows Taskbar. I implemented the native Windows Application Desktop Toolbar (AppBar) API using &lt;code&gt;SHAppBarMessage&lt;/code&gt; from &lt;code&gt;shell32.dll&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docked Mode:&lt;/strong&gt; By registering the application as an AppBar and setting the edge to &lt;code&gt;ABE_BOTTOM&lt;/code&gt;, Windows automatically recalculates the working area of the desktop. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Maximized windows are pushed upward, ensuring the light bar remains entirely visible and never covers any underlying application UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Floating Mode:&lt;/strong&gt; For users who need temporary access to the bottom of their screen, I added a state toggle that unregisters the AppBar and enables standard drag-and-drop window movement via &lt;code&gt;MouseLeftButtonDown&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enforcing a Single Instance
&lt;/h2&gt;

&lt;p&gt;Because the app directly manipulates the desktop working area, launching multiple overlapping instances would cause UI glitches. To prevent this, I implemented a &lt;code&gt;Mutex&lt;/code&gt; in &lt;code&gt;App.xaml.cs&lt;/code&gt; to guarantee a single instance constraint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnStartup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StartupEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"LightBarForKeyboard_UniqueInstance"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;createdNew&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;_mutex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;createdNew&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;createdNew&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnStartup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The OLED Efficiency Pivot
&lt;/h2&gt;

&lt;p&gt;Initially, the application featured a pure white bar. On standard LCD panels (IPS, TN, VA), the monitor's backlight is entirely on anyway. Displaying a white bar at the bottom of the screen simply twists the liquid crystals to let the existing light pass through. &lt;strong&gt;The extra power required is literally 0 W.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, OLED displays generate light per pixel. A full white bar firing all RGB subpixels across the bottom of a wide monitor consumes approximately &lt;strong&gt;2,0 W&lt;/strong&gt;. To make the software highly efficient on modern displays, we introduced custom color profiles targeting specific subpixels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure Green (RGB 0, 255, 0):&lt;/strong&gt; The human eye is highly sensitive to green light. This provides maximum contrast for reading black keyboard keys while bypassing the blue and red subpixels entirely. &lt;strong&gt;Consumption: ~0,5 W (75% savings).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure Red (RGB 255, 0, 0):&lt;/strong&gt; Excellent for protecting night vision, allowing the user to look away from the screen without eye strain. &lt;strong&gt;Consumption: ~0,3 W (85% savings).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yellow (RGB 255, 191, 0):&lt;/strong&gt; A warm, eye-friendly glow achieved by mixing red and green, completely keeping the power-hungry blue subpixel turned off. &lt;strong&gt;Consumption: ~0,8 W (60% savings).&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can seamlessly right-click the bar to switch between these profiles depending on their hardware and lighting preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment and SmartScreen Realities
&lt;/h2&gt;

&lt;p&gt;I packaged the project as a self-contained, single-file executable using .NET 10.0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-r&lt;/span&gt; win-x64 &lt;span class="nt"&gt;--self-contained&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;:PublishSingleFile&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;:IncludeNativeLibrariesForSelfExtract&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For distribution, I generated an Inno Setup installer. As an independent developer releasing free tools, purchasing an expensive EV Code Signing certificate isn't always practical. Consequently, early adopters will encounter the classic Windows Defender SmartScreen "Windows protected your PC" blue prompt. I added a quick disclaimer in the documentation to guide users to click &lt;strong&gt;More info&lt;/strong&gt; &amp;gt; &lt;strong&gt;Run anyway&lt;/strong&gt;, knowing that as the software gains organic downloads, its Microsoft SmartScreen reputation will naturally build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building this utility—and co-piloting the development process with Gemini—was a highly satisfying exercise in merging legacy Windows APIs with modern hardware considerations. It is a completely free, software-only solution to a hardware problem.&lt;/p&gt;

&lt;p&gt;If you want to try it out, check the code, or compile it yourself, the entire project is open-source on GitHub: &lt;a href="https://github.com/MediaExpres/LightBarForKeyboard" rel="noopener noreferrer"&gt;LightBarForKeyboard Repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>ai</category>
    </item>
    <item>
      <title>Fuse v4.3, an open source MCP/CLI tool to speed up Claude Code on C# codebases</title>
      <dc:creator>A. Shafie</dc:creator>
      <pubDate>Thu, 16 Jul 2026 07:59:26 +0000</pubDate>
      <link>https://dev.to/litenova/fuse-v42-an-open-source-mcpcli-tool-to-speed-up-claude-code-on-c-codebases-1f33</link>
      <guid>https://dev.to/litenova/fuse-v42-an-open-source-mcpcli-tool-to-speed-up-claude-code-on-c-codebases-1f33</guid>
      <description>&lt;p&gt;Fuse 4.2 is an open source .NET global tool that runs as an MCP server or from the command line.&lt;/p&gt;

&lt;p&gt;I built Fuse after watching coding agents repeatedly read and search the same .NET files. Across several turns, they would reconstruct symbols, references, dependency injection registrations, and project structure they had already encountered.&lt;/p&gt;

&lt;p&gt;On the write side, proposed C# changes often went through a longer edit and &lt;code&gt;dotnet build&lt;/code&gt; loop just to find an invalid member, missing argument, or incompatible type.&lt;/p&gt;

&lt;p&gt;Fuse addresses these two parts of the workflow: code discovery and earlier compiler feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusing .NET discovery
&lt;/h2&gt;

&lt;p&gt;Fuse loads the solution through MSBuild, analyzes C# with Roslyn, and stores the derived index locally in &lt;code&gt;.fuse/fuse.db&lt;/code&gt;. Changed files are updated incrementally, so later requests can reuse the existing index.&lt;/p&gt;

&lt;p&gt;The index supplies exact symbol lookup, .NET framework wiring, reduced task-scoped source, change impact, and Git-seeded branch review.&lt;/p&gt;

&lt;p&gt;For example, text search can find every occurrence of &lt;code&gt;IOrderService&lt;/code&gt;. Fuse can follow the dependency injection registration to the implementation used by the application. It can also resolve a MediatR request to its handler, an ASP.NET route to its action, or a configuration section to its options class.&lt;/p&gt;

&lt;p&gt;When Claude Code needs source, Fuse selects files from indexed anchors and reduces their content under a token budget. The response includes the reason each file was selected, and later calls in the same session can skip unchanged context already returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking proposed C# edits
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fuse_check&lt;/code&gt; accepts the proposed content of one C# file without changing the working tree.&lt;/p&gt;

&lt;p&gt;When compiler state captured from the real build is available, Fuse checks the proposal against that compilation. Otherwise, it falls back to a scoped build for the project that owns the file. If neither compiler path can run, Fuse abstains and reports what is missing.&lt;/p&gt;

&lt;p&gt;This check does not replace the final build or test run. It gives the coding agent compiler feedback earlier, before committing a proposed file to disk and starting the normal verification loop.&lt;/p&gt;

&lt;p&gt;Fuse also provides change-impact queries, covering-test selection, compiler-executed refactoring, and review context based on the current Git diff. These operations use the same local index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope and related tools
&lt;/h2&gt;

&lt;p&gt;Repository indexes and code graphs already exist. CodeGraphContext provides a local multi-language graph, Serena exposes language-server-backed symbol operations, Sourcegraph covers code search across repositories, and coding clients maintain their own indexes.&lt;/p&gt;

&lt;p&gt;The C# LSP also handles definitions, references, type information, live diagnostics, and editor refactoring. Fuse can run alongside it.&lt;/p&gt;

&lt;p&gt;Fuse has a narrower scope: local .NET analysis through MSBuild and Roslyn, including framework-specific wiring, reduced source for the current task, and checks against compiler state captured from the repository's real build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recorded results
&lt;/h2&gt;

&lt;p&gt;On the recorded NodaTime semantic index with 14,760 symbols, exact symbol lookup took 1.8 ms at the median. Task localization took 15.7 ms at the median.&lt;/p&gt;

&lt;p&gt;In a separate compiler-labeled suite over the OrderingApp test families, Fuse recorded 0 false green and 0 false red across 1,000 generated single-file edits plus 8 curated cases.&lt;/p&gt;

&lt;p&gt;Both results are bounded to the recorded machines, repositories, and test samples. The benchmark pages include the weaker results as well, including cases where open-ended localization has limited signal and the agent loop did not reduce the number of normal build and test calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Install Fuse as a .NET global tool and connect it to Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; Fuse
fuse mcp &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--client&lt;/span&gt; claude &lt;span class="nt"&gt;--rules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload Claude Code after installation. MCP read tools build the index on first use, or you can build it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fuse index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Analysis runs locally and can work offline. Fuse does not require a hosted service or its own model. The optional update check and builds using configured package feeds are the network-dependent cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/Litenova-Solutions/Fuse" rel="noopener noreferrer"&gt;github.com/Litenova-Solutions/Fuse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://fuse.codes" rel="noopener noreferrer"&gt;fuse.codes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Methods and limits: &lt;a href="https://fuse.codes/docs/project/benchmarks" rel="noopener noreferrer"&gt;fuse.codes/docs/project/benchmarks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reproduction: &lt;a href="https://fuse.codes/docs/project/reproduce" rel="noopener noreferrer"&gt;fuse.codes/docs/project/reproduce&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>ASP.NET Core mesCode</title>
      <dc:creator>mesCode</dc:creator>
      <pubDate>Thu, 16 Jul 2026 06:03:18 +0000</pubDate>
      <link>https://dev.to/mescode77/aspnet-core-mescode-342n</link>
      <guid>https://dev.to/mescode77/aspnet-core-mescode-342n</guid>
      <description>&lt;p&gt;In this backend development guide, we are setting up a secure RESTful API endpoint using ASP.NET Core Web API.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Database Configuration (appsettings.json)
&lt;/h2&gt;

&lt;p&gt;First, configure your SQL Server connection details inside the application environment variables. You can either use Windows Integrated Security or standard SQL Server Authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Integrated Security (Windows Auth)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;"AllowedHosts": "*",&lt;br&gt;
  "ConnectionStrings": {&lt;br&gt;
    "DefaultConnection": "Server=nameofserver;Database=nameofDB;Integrated Security=True;MultipleActiveResultSets=True;TrustServerCertificate=True;"&lt;br&gt;
  }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: SQL Server Authentication
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;"AllowedHosts": "*",&lt;br&gt;
  "ConnectionStrings": {&lt;br&gt;
    "DefaultConnection": "Server=nameofserver;Database=nameofDB;User Id=your_username;Password=your_password;MultipleActiveResultSets=True;TrustServerCertificate=True;"&lt;br&gt;
  }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defining the Service Contract (IService.cs)
To maintain loose coupling, we define an asynchronous interface containing standard CRUD contracts for our data mutations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Task&amp;lt;IEnumerable&amp;lt;Student&amp;gt;&amp;gt; GetAllStudentsAsync();&lt;br&gt;
   Task&amp;lt;Student&amp;gt; GetStudentByIDAsync(int id);&lt;br&gt;
   Task&amp;lt;int&amp;gt; AddStudentAsync(Student student);&lt;br&gt;
   Task&amp;lt;int&amp;gt; UpdateStudentAsync(Student student);&lt;br&gt;
   Task&amp;lt;int&amp;gt; DeleteStudentAsync(int id);&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Core Architecture: Async Stored Procedure Execution (StudentService.cs)
Here is the underlying service layer that handles optimal stream reading with native asynchronous ADO.NET extensions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;` public class StudentService(IConfiguration config) : IStudentService&lt;br&gt;
 {&lt;br&gt;
     private string connectionString = config.GetConnectionString("DefaultConnection")!;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; public async Task&amp;lt;IEnumerable&amp;lt;Student&amp;gt;&amp;gt; GetAllStudentsAsync()
 {
     try
     {
         var students = new List&amp;lt;Student&amp;gt;();
         using var connection = new SqlConnection(connectionString);
         using var command = new SqlCommand("[GetAllStudent]", connection)
         {
             CommandType = System.Data.CommandType.StoredProcedure,
         };
         await connection.OpenAsync();
         using var reader = await command.ExecuteReaderAsync();
         while (reader.Read())
         {
             Student student = new Student();
             {
                 student.StudentID = reader.GetInt32(reader.GetOrdinal("StudentID"));
                 student.StudentName = reader.GetString(reader.GetOrdinal("StudentName"));
                 student.Email = reader.GetString(reader.GetOrdinal("Email"));
                 student.Phone = reader.GetString(reader.GetOrdinal("Phone"));
                 student.Gender = reader.GetString(reader.GetOrdinal("Gender"));
                 student.Address = reader.GetString(reader.GetOrdinal("Address"));
                 student.Rank = reader.GetDecimal(reader.GetOrdinal("Rank"));
                 student.DOB = reader.GetDateTime(reader.GetOrdinal("DOB"));
                 student.CourseID = reader.GetInt32(reader.GetOrdinal("CourseID"));
                 student.CourseName = reader.GetString(reader.GetOrdinal("CourseName"));

             }
             ;
             students.Add(student);
         }
         return students;
     }
     catch (Exception ex)
     { 
         throw new Exception($"error retrirving Student: {ex.Message}", ex);
     }

 }
 public async Task&amp;lt;Student&amp;gt; GetStudentByIDAsync(int id)
 {
     try
     {

         using var connection = new SqlConnection(connectionString);
         using var command = new SqlCommand("[GetstudentbyID]", connection)
         {
             CommandType = System.Data.CommandType.StoredProcedure
         };
         command.Parameters.AddWithValue("@StudentID", id);
         await connection.OpenAsync();
         using var reader = await command.ExecuteReaderAsync();
         Student std = new Student();
         while (await reader.ReadAsync())
         {
             Student student = new Student();
             {
                 student.StudentID = reader.GetInt32(reader.GetOrdinal("StudentID"));
                 student.StudentName = reader.GetString(reader.GetOrdinal("StudentName"));
                 student.Email = reader.GetString(reader.GetOrdinal("Email"));
                 student.Phone = reader.GetString(reader.GetOrdinal("Phone"));
                 student.Gender = reader.GetString(reader.GetOrdinal("Gender"));
                 student.Address = reader.GetString(reader.GetOrdinal("Address"));
                 student.Rank = reader.GetDecimal(reader.GetOrdinal("Rank"));
                 student.DOB = reader.GetDateTime(reader.GetOrdinal("DOB"));
                 student.CourseID = reader.GetInt32(reader.GetOrdinal("CourseID"));
                 student.CourseName = reader.GetString(reader.GetOrdinal("CourseName"));
             }
             ;
             std = student;
         }
         return std;
     }
     catch (Exception ex)
     {
         throw new Exception($"Error Get student: {ex.Message}", ex);
     }
 }

 public async Task&amp;lt;int&amp;gt; AddStudentAsync(Student student)
 {
     try
     {
         using var connection = new SqlConnection(connectionString);
         using var command = new SqlCommand("[SaveStudent]", connection)
         {
             CommandType = System.Data.CommandType.StoredProcedure
         };

         command.Parameters.AddWithValue("@StudentID", student.StudentID);
         command.Parameters.AddWithValue("@StudentName", student.StudentName);
         command.Parameters.AddWithValue("@Email", student.Email);
         command.Parameters.AddWithValue("@Phone", student.Phone);
         command.Parameters.AddWithValue("@Gender", student.Gender);
         command.Parameters.AddWithValue("@Address", student.Address);
         command.Parameters.AddWithValue("@Rank", student.Rank);
         command.Parameters.AddWithValue("@DOB", student.DOB);
         command.Parameters.AddWithValue("@CourseID", student.CourseID);
         await connection.OpenAsync();
         var result = await command.ExecuteNonQueryAsync();
         return result;

     }
     catch (Exception ex)
     {
         throw new Exception($"Erro Add Student: {ex.Message}", ex);
     }

 }`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;RESTful API Presentation Layer (Controller.cs)
Expose the endpoints using attributes routing so that front-end modern frameworks can interact safely via HTTP request methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;` public class StudentController(IStudentService _studentService) : ControllerBase&lt;br&gt;
 {&lt;br&gt;
     [HttpGet("student")]&lt;br&gt;
     public async Task GetAllStudents()&lt;br&gt;
     {&lt;br&gt;
         try&lt;br&gt;
         {&lt;br&gt;
             var students = await _studentService.GetAllStudentsAsync();&lt;br&gt;
             return Ok(students);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     }
     catch (Exception ex)
     {
         return BadRequest(ex.Message);
     }

 }

 [HttpGet("{id:int}")]
 public async Task&amp;lt;IActionResult&amp;gt; GetStudentbyID(int id)
 {
     var student = await _studentService.GetStudentByIDAsync(id);
     if (student == null)
     {
         return NotFound($"Student With Id {id} not found");
     }
     return Ok(student);
 }

 [HttpPost]
 public async Task&amp;lt;IActionResult&amp;gt; AddStudent([FromBody] Student student)
 {
     var result = await _studentService.AddStudentAsync(student);
     return Ok(result);

 }
 [HttpPut("{id}")]
 public async Task&amp;lt;IActionResult&amp;gt; UpdateStudent(int id, [FromBody] Student student)
 {
     var result = await _studentService.UpdateStudentAsync(student);
     return NoContent();
 }

 [HttpDelete("{id:int}")]

 public async Task &amp;lt;IActionResult&amp;gt; DeleteStudent(int id)
 {
     var result = await _studentService.DeleteStudentAsync(id);
     return NoContent();
 }`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Dependency Injection &amp;amp; Service Registration (Program.cs)
Register the custom domain services and open global Cross-Origin Resource Sharing (CORS) rules to enable decoupling integrations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle&lt;br&gt;
builder.Services.AddEndpointsApiExplorer();&lt;br&gt;
builder.Services.AddSwaggerGen();&lt;br&gt;
_builder.Services.AddScoped&amp;lt;IStudentService,StudentService&amp;gt;();_&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;`// Configure the HTTP request pipeline.&lt;br&gt;
if (app.Environment.IsDevelopment())&lt;br&gt;
{&lt;br&gt;
    app.UseSwagger();&lt;br&gt;
    app.UseSwaggerUI();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;em&gt;app.UseCors(builder =&amp;gt;&lt;br&gt;
    builder.AllowAnyOrigin()&lt;br&gt;
           .AllowAnyMethod()&lt;br&gt;
           .AllowAnyHeader());&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>.NET 8 and .NET 9 Both Die on November 10 - Yes, the LTS Too</title>
      <dc:creator>endoflife-ai</dc:creator>
      <pubDate>Thu, 16 Jul 2026 02:21:19 +0000</pubDate>
      <link>https://dev.to/endoflifeai/net-8-and-net-9-both-die-on-november-10-yes-the-lts-too-1kig</link>
      <guid>https://dev.to/endoflifeai/net-8-and-net-9-both-die-on-november-10-yes-the-lts-too-1kig</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://endoflife.ai/article-dotnet-eol-2026" rel="noopener noreferrer"&gt;endoflife.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a date collision that surprises people: &lt;strong&gt;.NET 8 and .NET 9 reach end of support on the same day — November 10, 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instinctive reaction is "but 8 is the LTS!" — and that's exactly the trap. LTS doesn't mean &lt;em&gt;long&lt;/em&gt;, it means &lt;em&gt;longer&lt;/em&gt;. The math:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 8 (LTS)&lt;/strong&gt;: released November 2023, supported 3 years → dies November 10, 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET 9 (STS)&lt;/strong&gt;: released November 2024, supported 2 years → dies November 10, 2026&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different tracks, same cliff. Anyone who "played it safe" on the LTS and anyone who rode the STS end up in the identical position this November.&lt;/p&gt;

&lt;h2&gt;
  
  
  What end of support actually means
&lt;/h2&gt;

&lt;p&gt;After November 10, Microsoft ships &lt;strong&gt;no security patches&lt;/strong&gt; for either runtime — including for vulnerabilities that get actively exploited later. Your app keeps running; it just stops getting defended. CVEs published against the runtime after that date are permanent for you.&lt;/p&gt;

&lt;p&gt;For teams with compliance obligations, "unsupported runtime in production" is an audit finding, not a philosophical debate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only forward path is .NET 10
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;.NET 10 (LTS)&lt;/strong&gt; shipped November 11, 2025 and is supported to &lt;strong&gt;November 14, 2028&lt;/strong&gt;. For most 8→10 moves the upgrade is mercifully boring — retarget, run the test suite, chase a handful of breaking changes. The teams that get hurt are the ones who discover a transitive dependency pinned to an EOL runtime in &lt;em&gt;December&lt;/em&gt;, not the ones who retargeted in September.&lt;/p&gt;

&lt;p&gt;Worth separating in your head: &lt;strong&gt;.NET Framework 4.8&lt;/strong&gt; (the old Windows-only one) is a different animal — it has no fixed end date and lives as long as the Windows version hosting it. Modern .NET (Core lineage) is the one on the November clock. If you're still on Framework, your deadline is &lt;a href="https://endoflife.ai/article-windows-server-2012-esu-cliff" rel="noopener noreferrer"&gt;your Windows Server's deadline&lt;/a&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put a date on it
&lt;/h2&gt;

&lt;p&gt;The November 10 cutoff sits inside a brutal H2 2026 calendar: SharePoint 2016/2019 and SQL Server 2016 already died July 14, OpenSSL 3.0 goes September 7, Python 3.10 on October 31, PostgreSQL 14 on November 12, PHP 8.2 on December 31.&lt;/p&gt;

&lt;p&gt;Every date above is verified against vendor lifecycle sources. Full deadline coverage lives at &lt;a href="https://endoflife.ai/eol-watch" rel="noopener noreferrer"&gt;endoflife.ai/eol-watch&lt;/a&gt;, you can check any product's status in seconds with the &lt;a href="https://endoflife.ai/checker" rel="noopener noreferrer"&gt;EOL checker&lt;/a&gt;, or scan a whole project file with the &lt;a href="https://endoflife.ai/scanner" rel="noopener noreferrer"&gt;stack scanner&lt;/a&gt;. There's also a &lt;a href="https://endoflife.ai/api" rel="noopener noreferrer"&gt;free API&lt;/a&gt; if you'd rather gate this in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.endoflife.ai/v1/status/dotnet/8
&lt;span class="c"&gt;# "is_eol": false ... until November 10.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
      <category>security</category>
    </item>
  </channel>
</rss>
