<?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: Suresh Thotakura</title>
    <description>The latest articles on DEV Community by Suresh Thotakura (@thotakura).</description>
    <link>https://dev.to/thotakura</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F373840%2F32c9e5b9-3c69-4141-9c36-7aeedce1ebd7.jpeg</url>
      <title>DEV Community: Suresh Thotakura</title>
      <link>https://dev.to/thotakura</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thotakura"/>
    <language>en</language>
    <item>
      <title>A Rate Limiter That Knows Who's Calling</title>
      <dc:creator>Suresh Thotakura</dc:creator>
      <pubDate>Sat, 29 Aug 2026 19:47:21 +0000</pubDate>
      <link>https://dev.to/thotakura/a-rate-limiter-that-knows-whos-calling-i27</link>
      <guid>https://dev.to/thotakura/a-rate-limiter-that-knows-whos-calling-i27</guid>
      <description>&lt;p&gt;ASP.NET Core's rate limiter partitions however you key it. Key it on the caller, and one client's burst stops being every other client's problem.&lt;/p&gt;

&lt;p&gt;I built a small lab to get that wiring right end to end. &lt;/p&gt;

&lt;p&gt;Four stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Authenticate. A custom header scheme validates the client and issues a client_id claim. Nothing downstream can partition per client until this exists.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partition. The policy reads that claim and keys the partition by client id plus HTTP verb. A client's read budget and write budget are separate buckets, and so are two different clients'.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limit. Fixed window: 10 requests per 10 seconds, queue limit 0. No waiting in line, you either get a permit or you don't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enforce. Request 11 in the window gets HTTP 429.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The client's profile decides which partition it lands in:&lt;/p&gt;

&lt;p&gt;Standard: limited on every verb.&lt;br&gt;
Premium: reads skip the limiter, writes still get 10 per 10 seconds.&lt;br&gt;
Unlimited: no limiter at all.&lt;/p&gt;

&lt;p&gt;The part worth remembering is the ordering. &lt;br&gt;
UseRateLimiter() has to run after UseAuthentication() and UseAuthorization(). Register it earlier and the policy executes before the client_id claim exists, so there is nothing to partition on. In this implementation that throws rather than quietly limiting on nothing, which is the failure mode you want: loud, not silent.&lt;/p&gt;

&lt;p&gt;Three calls do the wiring: AddRateLimiter() to register the policy, UseRateLimiter() to put it in the pipeline, RequireRateLimiting() to attach it to an endpoint group.&lt;/p&gt;

&lt;p&gt;Code, including the in-memory client fixtures and the Todo endpoints it protects: &lt;a href="https://github.com/sthotakura/rate-limiter-lab" rel="noopener noreferrer"&gt;https://github.com/sthotakura/rate-limiter-lab&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aspnet</category>
      <category>ratelimiting</category>
      <category>aspdotnet</category>
    </item>
    <item>
      <title>Sealed Isn't a Restriction, It's a Promise</title>
      <dc:creator>Suresh Thotakura</dc:creator>
      <pubDate>Fri, 28 Aug 2026 18:53:06 +0000</pubDate>
      <link>https://dev.to/thotakura/sealed-isnt-a-restriction-its-a-promise-99e</link>
      <guid>https://dev.to/thotakura/sealed-isnt-a-restriction-its-a-promise-99e</guid>
      <description>&lt;p&gt;Leaving a class open to inheritance is a design decision, not a default you can ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;An unsealed class is a promise: every &lt;code&gt;virtual&lt;/code&gt; member can be overridden without breaking what the class guarantees. Most classes never meant to make that promise. They're just unsealed by default, because that's what &lt;code&gt;class&lt;/code&gt; gives you unless you say otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; treating &lt;code&gt;sealed&lt;/code&gt; as "I don't want to think about subclassing" rather than "this type's invariants would break if someone could."&lt;/p&gt;

&lt;h2&gt;
  
  
  One override breaks the promise
&lt;/h2&gt;

&lt;p&gt;Here's the promise, a &lt;code&gt;BankAccount&lt;/code&gt; that refuses to go negative:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&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="n"&gt;Balance&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="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;set&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;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Withdraw&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="k"&gt;if&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Balance&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the override that breaks it:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RiskyAccount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BankAccount&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="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Withdraw&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="n"&gt;Balance&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="c1"&gt;// no check&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here is exotic. It compiles cleanly, and &lt;code&gt;RiskyAccount&lt;/code&gt; is a&lt;br&gt;
perfectly legal &lt;code&gt;BankAccount&lt;/code&gt; as far as the type system is concerned. Open&lt;br&gt;
one with a balance of 100 and withdraw 500:&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;BankAccount&lt;/span&gt; &lt;span class="n"&gt;account&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;RiskyAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500m&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;$"Balance: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;F2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real &lt;code&gt;dotnet run&lt;/code&gt; output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Balance: -400.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check on the left never ran. &lt;code&gt;virtual&lt;/code&gt; was an open invitation, and&lt;br&gt;
&lt;code&gt;RiskyAccount&lt;/code&gt; took it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sealing turns a silent bug into a compile error
&lt;/h2&gt;

&lt;p&gt;Without &lt;code&gt;sealed&lt;/code&gt;, the code above compiles and produces a wrong answer at&lt;br&gt;
runtime; nothing points you at the problem until it's already in&lt;br&gt;
production. With &lt;code&gt;sealed&lt;/code&gt;, the same mistake becomes something the compiler&lt;br&gt;
catches before the code ever runs:&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="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&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="n"&gt;Balance&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="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;set&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;void&lt;/span&gt; &lt;span class="nf"&gt;Withdraw&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="k"&gt;if&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Balance&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="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;RiskyAccount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BankAccount&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// error CS0509: 'RiskyAccount': cannot derive&lt;/span&gt;
&lt;span class="c1"&gt;// from sealed type 'BankAccount'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sealing also makes &lt;code&gt;virtual&lt;/code&gt; on &lt;code&gt;Withdraw&lt;/code&gt; pointless, and the compiler&lt;br&gt;
enforces that too:&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="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankAccount&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="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Withdraw&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="cm"&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;// error CS0549: 'BankAccount.Withdraw(decimal)' is a new virtual&lt;/span&gt;
&lt;span class="c1"&gt;// member in sealed type 'BankAccount'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of those are real compiler errors, not paraphrased, verified against&lt;br&gt;
a live build.&lt;/p&gt;

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

&lt;p&gt;Seal by default. Unseal only when you intend to support inheritance. Every&lt;br&gt;
unsealed class is an implicit promise to every future subclass that&lt;br&gt;
overriding won't break its invariants. Most classes never meant to make&lt;br&gt;
that promise, they're just unsealed because nobody had to actively choose&lt;br&gt;
not to be.&lt;/p&gt;

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