{
    "version": "https://jsonfeed.org/version/1",
    "title": "Milan's .NET Weekly",
    "home_page_url": "https://milanjovanovic.tech",
    "feed_url": "https://milanjovanovic.tech/rss/feed.json",
    "description": "Every Saturday morning I will send you 1 actionable tip on .NET that you can easily implement.",
    "icon": "https://milanjovanovic.tech/profile.png",
    "author": {
        "name": "Milan Jovanović",
        "url": "https://milanjovanovic.tech"
    },
    "items": [
        {
            "id": "https://milanjovanovic.tech/blog/your-aspnetcore-endpoints-dont-have-a-timeout",
            "content_html": "<p>ASP.NET Core request timeouts can be configured globally or per endpoint with the built-in middleware in .NET 8 and later.\nThe middleware uses cooperative cancellation, so downstream work must observe <code>HttpContext.RequestAborted</code>.</p>\n<p>ASP.NET Core does not apply an application timeout to incoming requests by default.\nThe built-in request timeout middleware adds a deadline, but it only cancels <code>HttpContext.RequestAborted</code>.\nYour endpoint must pass that token into the work you want to stop.</p>\n<p>A reverse proxy might return its own timeout first, but then it controls the deadline and response instead of your application.</p>\n<p>A slow database query or stalled API call can keep consuming resources after its response is no longer useful.\n.NET 8 introduced <a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/timeouts\"><strong>request timeout middleware</strong></a> to give request processing a cooperative deadline.</p>\n<p>Let's wire it up.</p>\n<h2>Add a Timeout to the Endpoint</h2>\n<p>Register the middleware and apply a three-second timeout in <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddRequestTimeouts();\n\nvar app = builder.Build();\n\napp.UseRequestTimeouts();\n\napp.MapGet(&quot;/reports&quot;, async (\n    CancellationToken cancellationToken) =&gt;\n{\n    await Task.Delay(\n        TimeSpan.FromSeconds(10),\n        cancellationToken);\n\n    return Results.Ok(&quot;Ready&quot;);\n})\n.WithRequestTimeout(TimeSpan.FromSeconds(3));\n\napp.Run();\n</code></pre>\n<p><code>AddRequestTimeouts</code> only registers the required services.\nIt does not configure a limit by itself.</p>\n<p><code>WithRequestTimeout</code> gives this endpoint three seconds.\nMinimal APIs bind the <code>CancellationToken</code> parameter to <code>HttpContext.RequestAborted</code>.</p>\n<p>After three seconds, <code>Task.Delay</code> observes cancellation and throws.\nIf that exception reaches the middleware before the response starts, the default response is an empty <code>504 Gateway Timeout</code>.</p>\n<p>Test this without an attached debugger because the timeout does not trigger while a debugger is attached.</p>\n<h2>The Token Has to Reach the Work</h2>\n<p>The middleware does not abort a thread or call <code>HttpContext.Abort()</code>.\nIt cancels a token and keeps waiting for the endpoint.</p>\n<p>Remove <code>cancellationToken</code> from the <code>Task.Delay</code> call above and the handler waits the full ten seconds before returning <code>200 OK</code>.\nThere is no immediate <code>504</code> because no cancellation exception reaches the middleware.</p>\n<p>A trace-style span waterfall makes the difference visible:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_209/timeout_trace_waterfall.png\" alt=\"A trace-style span waterfall compares delayed work that observes RequestAborted and stops around the three-second deadline with work that ignores cancellation and returns 200 after ten seconds\">\n<p>The same rule applies to real dependencies.\nPass the token through your application service and into <a href=\"https://learn.microsoft.com/en-us/ef/core/miscellaneous/async\"><strong>EF Core</strong></a>:</p>\n<pre><code class=\"language-csharp\">public Task&lt;Order?&gt; GetByIdAsync(\n    Guid id,\n    CancellationToken cancellationToken)\n{\n    return dbContext.Orders\n        .AsNoTracking()\n        .SingleOrDefaultAsync(\n            order =&gt; order.Id == id,\n            cancellationToken);\n}\n</code></pre>\n<p>EF Core forwards the token to the database provider, which decides whether the operation can be canceled.\nThe token has to cross every boundary before the provider can see it:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_209/cancellation_propagation.png\" alt=\"The request timeout middleware cancels RequestAborted, which is passed through the endpoint, application service, and EF Core before the database provider can attempt to cancel the query\">\n<p>Pass it into <a href=\"https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet\"><strong>HttpClient</strong></a>, messaging clients, and other asynchronous work where abandoning the operation is safe.</p>\n<p>The same token is canceled when the client disconnects.\nFlowing it through the entire call chain matters even before you add a timeout.\nWhen a dependency honors cancellation, timed-out work stops instead of piling up under load.</p>\n<h2>Choose Timeouts Per Endpoint</h2>\n<p>Not every endpoint should share the same limit.\nA small API read and a report export have different latency budgets, so give them different policies.</p>\n<p>Replace the parameterless registration with named policies, then attach each one when mapping the endpoint:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRequestTimeouts(options =&gt;\n{\n    options.AddPolicy(&quot;api-read&quot;, TimeSpan.FromSeconds(3));\n    options.AddPolicy(&quot;report-export&quot;, TimeSpan.FromSeconds(30));\n});\n\napp.MapGet(&quot;/orders/{id:guid}&quot;, GetOrder)\n    .WithRequestTimeout(&quot;api-read&quot;);\n\napp.MapGet(&quot;/reports/{id:guid}&quot;, ExportReport)\n    .WithRequestTimeout(&quot;report-export&quot;);\n\napp.MapGet(&quot;/events&quot;, StreamEvents)\n    .DisableRequestTimeout();\n</code></pre>\n<p>Small reads get three seconds, report exports get 30 seconds, and the streaming endpoint opts out.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/server-sent-events-in-aspnetcore-and-dotnet-10\"><strong>Server-Sent Events</strong></a>, WebSockets, long polling, and large uploads usually need a longer policy or <code>.DisableRequestTimeout()</code>.\nOnce a streaming response starts, the middleware cannot replace it with a clean <code>504</code>.</p>\n<p>If an operation genuinely needs minutes, return <code>202 Accepted</code> and finish it in the background, as described in <a href=\"https://milanjovanovic.tech/blog/how-to-scale-long-running-api-requests\"><strong>scaling long-running API requests</strong></a>.</p>\n<h2>Summary</h2>\n<p>A <code>504</code> from the middleware tells you that cancellation reached it.\nIt does not prove that every downstream operation stopped.</p>\n<p>Start with one endpoint that calls EF Core or <code>HttpClient</code>.\nSet a realistic limit and force a slow call past it without a debugger attached.\nUse logs or a trace to verify that the dependency observed cancellation.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/your-aspnetcore-endpoints-dont-have-a-timeout",
            "title": "Your ASP.NET Core Endpoints Don't Have a Timeout",
            "summary": "ASP.NET Core doesn't enforce an application timeout on incoming requests by default. The built-in middleware can add one, but it only cancels RequestAborted.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_209.png",
            "date_modified": "2026-08-29T00:00:00.000Z",
            "date_published": "2026-08-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-design-the-right-sql-index",
            "content_html": "<p>A good SQL index comes from the queries your application runs, not from the table schema.\nComposite indexes need the right column order: equality columns first, then the column you sort or range on.\n<code>EXPLAIN ANALYZE</code> is how you verify it: a sequential scan over 1 million comments takes 17ms, and the right composite index answers in 0.04ms.</p>\n<p>What does a good SQL index look like?</p>\n<p>The answer will vary based on your queries and access paths.\nThe only way to confidently know is to examine the query plans with <code>EXPLAIN ANALYZE</code> and figure out from there which index might help.</p>\n<p>So let's do exactly that.\nI seeded a <a href=\"https://www.postgresql.org\"><strong>Postgres</strong></a> 18 instance in Docker with an issue tracker: 100 users, 10,000 issues, and 1 million comments.\nBy the end, one query drops from 436ms to half a millisecond.</p>\n<h2>What Is a SQL Index?</h2>\n<p>An index stores your chosen columns in sorted order, with every entry pointing back to its full row.\nThe default kind in every major database is the <strong>B-tree</strong>: a shallow tree, a few levels deep even at millions of rows.\nA sequential scan reads all 1 million comments; an index scan descends those few levels and fetches only the matches.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_208/seq_scan_vs_index_walk.png\" alt=\"Side by side comparison of a sequential scan reading every row in a one million row comments table versus a B-tree index scan descending three levels and fetching only the matching rows\">\n<h2>Start With the Query, Not the Table</h2>\n<p>You don't pick indexes by staring at the schema; they come from the queries your application actually runs.</p>\n<p>My <code>comments</code> table serves three access patterns:</p>\n<ul>\n<li>All comments by a user</li>\n<li>All comments for an issue</li>\n<li>Comments for an issue from one user, newest first, last month only</li>\n</ul>\n<h2>Reading the First Plan</h2>\n<p>The first pattern, with no index beyond the primary key:</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE\nSELECT COUNT(*)\nFROM comments\nWHERE user_id = 1;\n\n---\nFinalize Aggregate\n  -&gt;  Gather\n        -&gt;  Partial Aggregate\n              -&gt;  Parallel Seq Scan on comments  (actual time=0.010..11.727 rows=3356.67 loops=3)\n                    Filter: (user_id = 1)\n                    Rows Removed by Filter: 329977\nExecution Time: 17.066 ms\n</code></pre>\n<p><code>EXPLAIN ANALYZE</code> runs the query for real and prints the plan Postgres used: a <code>Parallel Seq Scan</code> reads all 1 million rows to count 10,070, in <strong>17ms</strong>.</p>\n<p>Create the index and rerun the query:</p>\n<pre><code class=\"language-sql\">CREATE INDEX ix_comments_user_id\nON comments (user_id);\n</code></pre>\n<pre><code class=\"language-sql\">Aggregate\n  -&gt;  Index Only Scan using ix_comments_user_id on comments  (actual time=0.024..0.348 rows=10070.00 loops=1)\n        Index Cond: (user_id = 1)\n        Heap Fetches: 0\nExecution Time: 0.612 ms\n</code></pre>\n<p>17ms down to <strong>0.6ms</strong>.\nIt's an <code>Index Only Scan</code> because the index alone can answer a <code>COUNT(*)</code>: Postgres never touches the table.</p>\n<h2>Column Order Is Everything</h2>\n<p>The third access pattern is the interesting one:</p>\n<pre><code class=\"language-sql\">SELECT *\nFROM comments\nWHERE issue_id = 10\n  AND user_id = 29\n  AND created_at &gt;= NOW() - INTERVAL '1 month'\nORDER BY created_at DESC;\n</code></pre>\n<p>With no index, it's another sequential scan: <strong>16.6ms</strong>.\nWith single-column indexes on <code>issue_id</code> and <code>user_id</code>, Postgres intersects them with a <code>BitmapAnd</code> and still sorts the survivors: <strong>0.6ms</strong>, in three steps.</p>\n<p>A composite index answers the whole query in one motion:</p>\n<pre><code class=\"language-sql\">CREATE INDEX ix_comments_issue_user_date\nON comments (issue_id, user_id, created_at DESC);\n</code></pre>\n<pre><code class=\"language-sql\">Index Scan using ix_comments_issue_user_date on comments  (actual time=0.019..0.026 rows=2.00 loops=1)\n  Index Cond: ((issue_id = 10) AND (user_id = 29) AND (created_at &gt;= (now() - '1 mon'::interval)))\nExecution Time: 0.039 ms\n</code></pre>\n<p>All three conditions moved into the <code>Index Cond</code>, and the <code>Sort</code> is gone: the index already returns rows ordered by <code>created_at DESC</code>.\nRuntime: <strong>0.04ms</strong>, over 400x faster.</p>\n<p>A composite index sorts by its first column, then the second within equal values, then the third.\nPostgres jumps straight to the <code>issue_id = 10, user_id = 29</code> section and reads it in order.</p>\n<p>Column order also decides what else the index can serve: <code>issue_id</code> alone works, <code>issue_id</code> plus <code>user_id</code> works, but <code>user_id</code> alone doesn't (its values are scattered across the whole tree).\nThis is the <strong>leftmost prefix rule</strong>, and it's why the index on <code>user_id</code> stays.</p>\n<p>The rule of thumb: <strong>equality columns first, then the column you sort or range on</strong>.</p>\n<h2>The Query Our New Index Can't Serve</h2>\n<p>Every issue tracker runs this dashboard query: the 25 newest open issues, each with its latest comment, fetched by a <code>LATERAL</code> subquery:</p>\n<pre><code class=\"language-sql\">SELECT i.id, c.body, c.created_at\nFROM issues i\nCROSS JOIN LATERAL (\n  SELECT body, created_at\n  FROM comments\n  WHERE issue_id = i.id\n  ORDER BY created_at DESC\n  LIMIT 1\n) c\nWHERE i.status = 'open'\nORDER BY i.created_at DESC\nLIMIT 25;\n</code></pre>\n<pre><code class=\"language-sql\">Nested Loop  (actual time=0.790..352.076 rows=6537.00 loops=1)\n  -&gt;  Seq Scan on issues i  (rows=6537.00 loops=1)\n  -&gt;  Limit  (rows=1.00 loops=6537)\n        -&gt;  Sort  (actual time=0.053..0.053 rows=1.00 loops=6537)\n              -&gt;  Bitmap Index Scan on ix_comments_issue_user_date  (loops=6537)\nExecution Time: 435.794 ms\n</code></pre>\n<p>The composite index gets used, but its entries are sorted by <code>user_id</code> before <code>created_at</code>, so a <code>Sort</code> runs 6,537 times, once per open issue: <strong>436ms</strong>.</p>\n<p>Column order strikes again.\nFor this access path, <code>created_at</code> must come right after <code>issue_id</code>:</p>\n<pre><code class=\"language-sql\">CREATE INDEX ix_comments_issue_date\nON comments (issue_id, created_at DESC);\n</code></pre>\n<p>Each probe becomes a one-row index scan: <strong>25ms</strong>.\nBut the <code>LIMIT</code> still can't stop the loop, because issues arrive unsorted.\nOne more index streams them newest-first:</p>\n<pre><code class=\"language-sql\">CREATE INDEX ix_issues_status_date\nON issues (status, created_at DESC);\n</code></pre>\n<pre><code class=\"language-sql\">Limit  (actual time=0.086..0.465 rows=25.00 loops=1)\n  -&gt;  Nested Loop  (actual time=0.085..0.463 rows=25.00 loops=1)\n        -&gt;  Index Scan using ix_issues_status_date on issues i  (rows=25.00 loops=1)\n        -&gt;  Limit  (rows=1.00 loops=25)\n              -&gt;  Index Scan using ix_comments_issue_date on comments  (rows=1.00 loops=25)\nExecution Time: 0.489 ms\n</code></pre>\n<p>Every node reads only what it returns: 25 issues, 25 probes, one comment each.\n<strong>0.5ms</strong>, nearly 900x faster.</p>\n<h2>What Do Indexes Cost?</h2>\n<p>Every insert, update, and delete now maintains every index, so each one you add slows writes a little.\nThey take disk space, too:</p>\n<pre><code class=\"language-sql\">SELECT indexrelname AS index_name,\n       pg_size_pretty(pg_relation_size(indexrelid)) AS size\nFROM pg_stat_user_indexes\nWHERE relname = 'comments';\n</code></pre>\n<p>Each composite index weighs <strong>30 MB</strong> for 1 million comments, against about 7 MB per single-column one.\nAnd <code>(issue_id, created_at DESC)</code> makes the plain <code>issue_id</code> index redundant, so drop it.\nIndex the queries you actually run, not the ones you might run someday.</p>\n<p>An index only helps if the query can use it: wrap the indexed column in a function and Postgres ignores it, a failure mode I covered in <a href=\"https://milanjovanovic.tech/blog/sql-index-not-used-sargability\"><strong>Why Postgres Ignores Your Index</strong></a>.</p>\n<h2>Summary</h2>\n<ul>\n<li><strong>Design indexes from your queries, not your tables.</strong></li>\n<li><strong>Composite indexes need the right column order</strong>: equality columns first, then the sort column.</li>\n<li><strong><code>LIMIT</code> only helps when an index feeds it rows already in order.</strong></li>\n<li><strong><code>EXPLAIN ANALYZE</code> is the proof.</strong> Read the plan, not just the timing.</li>\n<li><strong>Every index costs writes and space.</strong></li>\n</ul>\n<p>Once the indexes are right, <a href=\"https://milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive\"><strong>cursor pagination</strong></a> is the natural next step, built on exactly these composite indexes.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-design-the-right-sql-index",
            "title": "From 17ms to 0.04ms: How to Design the Right SQL Index",
            "summary": "What does a good SQL index look like? I seeded Postgres with 1 million comments and measured every indexing decision with EXPLAIN ANALYZE: a 17ms sequential…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_208.png",
            "date_modified": "2026-08-22T00:00:00.000Z",
            "date_published": "2026-08-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-do-you-build-a-social-media-feed-system-design",
            "content_html": "<p>A home feed starts as fan-out on read: query the posts of every account you follow, merge, sort, return a page.\nThat work moves to write time once reads outnumber writes, with workers appending post ids to a per-follower timeline cache.\nCelebrity accounts break that too, and hybrid fan-out merges their posts in at read time instead.</p>\n<p>Every social app has a home feed, and it looks like the easiest feature in the product.</p>\n<p>Fetch the recent posts from every account the user follows, merge them, sort by time, return the first page.\nYou could ship it in an afternoon.</p>\n<p>Feeds are a classic system design problem because the obvious version does the work at read time, and the version that survives production does it at write time.\nLet's build one and watch where each version breaks.</p>\n<h2>Fan-Out on Read</h2>\n<p>The naive feed computes everything at read time: <strong>fan-out on read</strong>.</p>\n<p>The feed is a query.\nWhen a user opens the app, the Feed API runs the merge live, against the posts of every account they follow.\nA user following 800 accounts turns one page view into an 800-way merge that runs before anything renders, and pull-to-refresh throws the result away and runs it again.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/feed_read_storm.png\" alt=\"A client requests its feed, and the Feed API fans out reads to the posts of every followed author in the post store, merging and sorting the results on every refresh\">\n<p>To be fair to the naive design: with a composite index on <code>(author_id, created_at)</code>, that merge is a single query that returns in milliseconds, and it stays fine well past the point most products ever reach.\nIf your users follow a few hundred accounts and you serve hundreds of feed reads per second, ship the query and move on.</p>\n<p>The problem is the ratio.\nTwitter published its numbers years ago: roughly 300,000 home timeline reads per second against about 5,000 new tweets.\nSixty reads for every write is the ratio that justifies moving the work to the write side.</p>\n<h2>The Single-Writer Rule</h2>\n<p>Before fixing reads, the write path needs one property: a single writer.</p>\n<p>Public traffic enters through an API gateway, and only the Post API writes the post store.\nThe gateway already handles authentication.\nWhat the single writer buys is one transactional boundary: committing a post and recording its event happen atomically, in commit order.</p>\n<p>Split posting across two services and you get two event streams with no shared order, where a delete can race ahead of the create it refers to.\nIt also gives your <a href=\"https://milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them\"><strong>invariants</strong></a> one home.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/write_path.png\" alt=\"A client publishes through an API gateway to the Post API, which is the only writer of the post store, while a blocked red path shows that direct client writes are not allowed\">\n<p>Fan-out on write depends on this: every committed post produces exactly one event.</p>\n<h2>Fan-Out on Write</h2>\n<p>The first fix most teams reach for is caching the merged page per user with a short TTL, and it does absorb repeat refreshes.\nBut a feed cache entry serves exactly one user, so the hit rate is only as good as how often that user reloads within the window, and every expiry brings back the full merge.\n<strong>Fan-out on write</strong> is that cache with the timer removed: kept correct by writes instead of rebuilt on expiry, and populated for every follower whether or not they ever read.</p>\n<p>When an author publishes a post:</p>\n<ol>\n<li>The Post API commits the post to the post store.</li>\n<li>It publishes a small immutable event to a <a href=\"https://milanjovanovic.tech/blog/getting-started-with-nats-jetstream-in-dotnet\"><strong>topic</strong></a>: author id, post id, timestamp.</li>\n<li>Fan-out workers consume the event, expand the author into their follower list, and append the post id to each follower's <strong>timeline</strong>: a capped list of recent post ids in a cache.</li>\n</ol>\n<p>The Feed API serves a feed with one timeline lookup and a batched fetch to hydrate the ids into posts.\nScroll past the cap and the feed falls back to the query path.\nAlmost nobody does.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/fan_out_on_write.png\" alt=\"An author\">\n<p>Five decisions make this hold up in production:</p>\n<ul>\n<li><strong>Store ids, not posts.</strong> Twitter capped home timelines at roughly 800 entries, and 800 ids is about 10 KB per user: 100 million users fit in a terabyte of cache. Full 2 KB post documents would need 160 TB.</li>\n<li><strong>Enforce policy at hydration.</strong> Deleted posts drop out as hydration misses, block filters run on every request, and an unfollow needs no cleanup: the author's entries age out past the cap.</li>\n<li><strong>The projection is disposable.</strong> A lost timeline is rebuilt from the post store with the naive query, once per cold user. The catch: a dead cache node sends every user on it cold at once, so cap rebuild concurrency or the post store inherits a read storm.</li>\n<li><strong>Publish the event if and only if the post commits.</strong> Publish before commit and you announce posts that don't exist; publish after and a crash can lose the event. The <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a> closes both gaps.</li>\n<li><strong>Chunk the fan-out.</strong> <a href=\"https://milanjovanovic.tech/blog/nats-jetstream-job-queue-dotnet\"><strong>Competing consumers</strong></a> parallelize across posts, not within one, so a big expansion is split into follower-range sub-jobs that workers share. Appends are <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>idempotent</strong></a>, keyed by post id, so a redelivered chunk touches nothing already written.</li>\n</ul>\n<p>The tradeoff: <strong>eventual consistency</strong>.\nThe first user to notice is the author: they publish, refresh, and their own post is missing.</p>\n<p>The fix is read-your-own-writes at the feed edge: the Feed API merges the reader's own recent posts in at read time.\nFollowers seeing a post a few seconds late is the part nobody notices.</p>\n<h2>Write Amplification</h2>\n<p>Fan-out on write has a multiplier hiding in step 3: every publish costs one timeline write per follower.</p>\n<p>For an account with 300 followers, that's 300 small cache appends.\nCheap.</p>\n<p>But follower counts follow a power-law distribution.\nAn account with 50 million followers publishes once, and the workers now owe the cache 50 million writes.</p>\n<p>The 60-to-1 ratio justified paying per write because writes were rare and each append was cheap.\nA post that becomes 50 million appends, most of them into timelines nobody will open, breaks both premises.\nEven the expansion is heavy: 50 million follower rows paged out of the graph store just to know where to write.</p>\n<p>While the workers grind through it, two things go wrong for everyone else:</p>\n<ul>\n<li><strong>Consumer lag.</strong> The celebrity's chunks tie up worker capacity for minutes, the backlog ages, and every feed behind it goes stale.</li>\n<li><strong>Cache churn.</strong> Allocating timeline entries for tens of millions of mostly inactive followers pressures the cache, and warm timelines are evicted to make room.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/write_amplification.png\" alt=\"A celebrity post at the head of the topic ties up the fan-out workers with 50 million cache appends, while the backlog of ordinary posts behind it grows and warm feeds are evicted from the timeline cache\">\n<p>Two mitigations come before a redesign.\nFan out only to followers who were active recently, and rebuild dormant timelines on their next visit (the disposable projection already paid for that path).\nRoute the biggest accounts to their own queue, so ordinary posts stop waiting behind them.</p>\n<p>What neither does is shrink the work: the biggest accounts still owe millions of writes per post, and the cache pressure lands regardless.</p>\n<h2>Hybrid Fan-Out</h2>\n<p>No single strategy serves both ends of a power-law distribution.</p>\n<p>Ordinary authors keep fan-out on write.\nBounded follower sets make write-time work cheap, and reads stay one lookup.</p>\n<p>Celebrity authors switch to a narrow form of fan-out on read.\nTheir posts are appended to a compact <strong>celebrity index</strong>, keyed by author, and publishing becomes one write regardless of follower count.</p>\n<p>At read time, the Feed API merges the user's materialized timeline with recent candidates from the celebrity indexes they follow, deduplicates, and hydrates.\nA follower-count threshold or cost model decides which path an author uses.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/hybrid_feed.png\" alt=\"Ordinary posts flow through the topic to fan-out workers that write follower timelines, celebrity posts land in a compact celebrity index with one write per post, and the Feed API merges both sources at read time before returning the feed to the reader\">\n<p>Twitter described this same architecture publicly years ago: home timelines materialized in <a href=\"https://redis.io\"><strong>Redis</strong></a> by fan-out workers, with the highest-follower accounts merged in at read time.</p>\n<p>The tradeoffs:</p>\n<ul>\n<li><strong>Reads get more complex and less predictable.</strong> A user following many celebrity accounts pays multiple index reads and a bigger merge on every page.</li>\n<li><strong>Pagination needs a cursor per source.</strong> Where the merge stopped in the timeline, plus the last id consumed from each celebrity index. Resuming each source from its own position is what keeps a mid-scroll post from duplicating or vanishing.</li>\n<li><strong>The threshold flaps.</strong> Promotion can put a post in both sources, which the merge deduplicates by post id. Demotion is the dangerous direction: posts that live only in the index vanish from feeds unless you backfill them into follower timelines or keep merging the demoted index for a grace window.</li>\n</ul>\n<h2>Ranked Feeds</h2>\n<p>Everything so far assumes reverse-chronological order, which is the feed that Twitter talk describes.\nNone of the big networks ship that as the default anymore.</p>\n<p>A ranked feed keeps the same plumbing and adds a funnel on the read path:</p>\n<ol>\n<li><strong>Candidate generation.</strong> The materialized timeline and celebrity indexes become candidate sources, joined by out-of-network sources: posts from accounts you don't follow, retrieved by embedding similarity and graph signals.</li>\n<li><strong>Light ranking.</strong> A cheap model trims thousands of candidates to a few hundred, because the good model is too expensive to run on everything.</li>\n<li><strong>Heavy ranking.</strong> A neural model scores each surviving post by predicting engagement probabilities (like, reply, repost, dwell time) and combining them into one weighted score.</li>\n<li><strong>Re-ranking.</strong> Product rules run last: author diversity, integrity filters, blocked-content removal, ad slots.</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/ranked_feed_funnel.png\" alt=\"The timeline cache, celebrity index, and out-of-network sources feed candidate generation, a light ranker trims thousands of posts to a few hundred, a heavy ranker scores them by predicted engagement, and re-ranking rules produce the final page\">\n<p>When Twitter open-sourced its recommendation algorithm in 2023, this was the shape: roughly half the candidates in-network, half out-of-network, funneled through a light ranker into a neural &quot;heavy ranker&quot; that predicts engagement.</p>\n<p>None of it replaces the fan-out machinery.\nThe timeline you materialized is still there; it became one candidate source among several, and the merge became a scoring stage.</p>\n<h2>Operating the Pipeline</h2>\n<p>Most of this system is asynchronous, so operating it means watching lag rather than error rates.</p>\n<ul>\n<li><strong>Consumer lag.</strong> The age of the oldest unprocessed publish event. The first number to alarm on.</li>\n<li><strong>Fan-out writes per post, by author.</strong> The number a per-author throttle acts on. One author dominating worker time means the celebrity threshold is set wrong.</li>\n<li><strong>Commit-to-visible latency.</strong> From transaction commit until the post shows up in follower timelines, tracked at a high percentile. The first follower gets it in milliseconds; the 50 millionth is the number that matters.</li>\n<li><strong>Feed cache hit rate.</strong> A dropping hit rate is the early symptom of cache churn.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_207/pipeline_metrics.png\" alt=\"The pipeline from Post API through topic, workers, and timeline cache to the Feed API, annotated with the lag metric each stage exposes and a commit-to-visible latency span across the whole path\">\n<h2>Summary</h2>\n<p>The whole progression:</p>\n<ul>\n<li><strong>Fan-out on read</strong>: the feed is a query; fine until the read-to-write ratio makes it the most expensive path in the product.</li>\n<li><strong>Single writer</strong>: one transactional boundary, so every committed post produces exactly one event.</li>\n<li><strong>Fan-out on write</strong>: materialize timelines at publish time; reads become one lookup, and the projection stays disposable.</li>\n<li><strong>Write amplification</strong>: one celebrity post becomes 50 million writes, and everyone else pays.</li>\n<li><strong>Hybrid fan-out</strong>: ordinary authors fan out on write; celebrity posts sit in a compact index that the Feed API merges in at read time.</li>\n</ul>\n<p>If you'd rather design this than read about it, I just launched <strong>System Design Studio</strong> on <a href=\"https://katabench.com/system-design\"><strong>Katabench</strong></a>.\nYou wire components on a canvas, and a grader checks your topology, then explains what it can and cannot prove.</p>\n<p>The three challenges behind this article (the write path, timeline fan-out, celebrity fan-out) are free.\nStart with the celebrity fan-out, and hit reply if the grader disagrees with you.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-do-you-build-a-social-media-feed-system-design",
            "title": "How Do You Build a Social Media Feed? (System Design)",
            "summary": "The home feed looks like the easiest feature in a social app: fetch posts from the accounts you follow, sort, return a page.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_207.png",
            "date_modified": "2026-08-15T00:00:00.000Z",
            "date_published": "2026-08-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core",
            "content_html": "<p>EF Core does not need public setters or a public parameterless constructor.\nIt builds entities through a private constructor and reads and writes state through backing fields, so a fully encapsulated aggregate loads and saves like any other entity.\nStrongly typed IDs, private collections, value objects, and domain events are all handled in configuration classes the domain never references.</p>\n<p>Whenever I write about <a href=\"https://milanjovanovic.tech/blog/from-anemic-models-to-behavior-driven-models-a-practical-ddd-refactor-in-csharp\"><strong>refactoring an anemic domain model into a rich one</strong></a>, one objection reliably appears in the replies:</p>\n<blockquote>\n<p>Nice in theory, but EF Core needs public setters and a public parameterless constructor. The ORM forces the anemic model on us.</p>\n</blockquote>\n<p>It <em>was</em> true a decade ago.\nIt is not true now: EF Core will happily persist a fully encapsulated aggregate, and the payoff is a domain model that <a href=\"https://milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them\"><strong>enforces its invariants</strong></a> in one place while the ORM quietly does its job.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_206/anemic_vs_rich_aggregate.png\" alt=\"An anemic entity with public setters that any service can mutate, next to a rich aggregate where state is private and every change goes through methods that enforce invariants\">\n<p>Let's map one aggregate end to end and hit every place where EF Core and encapsulation supposedly collide.</p>\n<h2>The Aggregate We Want to Persist</h2>\n<p>The domain is home brewing, because order aggregates have been done to death.\nA <code>Batch</code> ferments, you take gravity readings, and once the gravity holds steady you bottle.\nBottle too early, while the yeast is still eating sugar, and you get bottle bombs.</p>\n<p>Here's the <code>Batch</code> written the way I actually want it: no public setters, creation through a factory, and state changes that go through methods.</p>\n<pre><code class=\"language-csharp\">public sealed class Batch\n{\n    private readonly List&lt;FermentationReading&gt; _readings = [];\n    private readonly List&lt;IDomainEvent&gt; _domainEvents = [];\n    private DateTime? _bottledAtUtc;\n\n    private Batch() { } // For EF Core\n\n    private Batch(BatchId id, RecipeId recipeId, Volume volume)\n    {\n        Id = id;\n        RecipeId = recipeId;\n        Volume = volume;\n        Status = BatchStatus.Fermenting;\n    }\n\n    public BatchId Id { get; private set; }\n    public RecipeId RecipeId { get; private set; }\n    public BatchStatus Status { get; private set; }\n    public Volume Volume { get; private set; }\n\n    public IReadOnlyCollection&lt;FermentationReading&gt; Readings =&gt; _readings.AsReadOnly();\n    public IReadOnlyCollection&lt;IDomainEvent&gt; DomainEvents =&gt; _domainEvents.AsReadOnly();\n\n    public static Batch Start(RecipeId recipeId, Volume volume) =&gt;\n        new(new BatchId(Guid.CreateVersion7()), recipeId, volume);\n\n    public void AddReading(Gravity gravity, DateTime takenAtUtc)\n    {\n        if (Status != BatchStatus.Fermenting)\n        {\n            throw new DomainException(&quot;Readings only make sense while the batch is fermenting.&quot;);\n        }\n\n        _readings.Add(new FermentationReading(gravity, takenAtUtc));\n    }\n\n    public void Bottle(TimeProvider timeProvider)\n    {\n        if (Status != BatchStatus.Fermenting)\n        {\n            throw new DomainException(&quot;Only a fermenting batch can be bottled.&quot;);\n        }\n\n        Gravity[] lastTwo = _readings\n            .OrderBy(r =&gt; r.TakenAtUtc)\n            .TakeLast(2)\n            .Select(r =&gt; r.Gravity)\n            .ToArray();\n\n        if (lastTwo.Length &lt; 2 || lastTwo[0] != lastTwo[1])\n        {\n            throw new DomainException(\n                &quot;Gravity must hold steady across two readings before bottling.&quot;);\n        }\n\n        Status = BatchStatus.Bottled;\n        _bottledAtUtc = timeProvider.GetUtcNow().UtcDateTime;\n        _domainEvents.Add(new BatchBottled(Id));\n    }\n\n    public void ClearDomainEvents() =&gt; _domainEvents.Clear();\n}\n</code></pre>\n<p>The supporting types are <a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>records</strong></a>, so value equality comes for free:</p>\n<pre><code class=\"language-csharp\">public readonly record struct BatchId(Guid Value);\npublic readonly record struct RecipeId(Guid Value);\npublic readonly record struct Gravity(decimal Value);\n\npublic sealed record Volume(decimal Amount, string Unit);\n</code></pre>\n<p>Three choices here come straight from <strong>aggregate design</strong>: <code>RecipeId</code> references another aggregate by ID only, <code>FermentationReading</code> is a child entity that lives and dies with the batch, and <code>_bottledAtUtc</code> is a private field with no property at all.\nEvery one of those supposedly &quot;breaks&quot; EF Core, so let's map them piece by piece.</p>\n<h2>Private Constructors and Private Setters Just Work</h2>\n<p>When EF Core materializes an entity, it doesn't use your public API.\nIt calls the <strong>private parameterless constructor</strong> and writes to properties <strong>through their backing fields</strong>, private setters and all.\nThat's the whole reason <code>private Batch() { }</code> exists, and it's the one concession the domain model makes to the ORM.</p>\n<p>Loading and changing a batch looks like any other EF code:</p>\n<pre><code class=\"language-csharp\">Batch batch = await context.Batches\n    .SingleAsync(b =&gt; b.Id == batchId);\n\nbatch.AddReading(new Gravity(1.012m), timeProvider.GetUtcNow().UtcDateTime);\n\nawait context.SaveChangesAsync();\n</code></pre>\n<p>Change tracking reads the same backing fields, so private setters hide nothing from <code>SaveChanges</code>.</p>\n<p>EF can even bind a <strong>parameterized constructor</strong>, matching parameters to mapped properties by name and type, private or not.\nThe catch: navigations can't be constructor-bound, so an aggregate with a collection still needs the parameterless one.\nEF also skips your factory's validation when loading, which is correct: re-running rules during materialization would make historical rows unloadable the first time a rule changes.</p>\n<h2>Strongly Typed IDs and References to Other Aggregates</h2>\n<p><code>BatchId</code> and <code>RecipeId</code> are <strong>strongly typed IDs</strong>, mapped with value conversions inside an <code>IEntityTypeConfiguration&lt;Batch&gt;</code>:</p>\n<pre><code class=\"language-csharp\">public sealed class BatchConfiguration : IEntityTypeConfiguration&lt;Batch&gt;\n{\n    public void Configure(EntityTypeBuilder&lt;Batch&gt; builder)\n    {\n        builder.ToTable(&quot;batches&quot;);\n\n        builder.HasKey(b =&gt; b.Id);\n\n        builder.Property(b =&gt; b.Id)\n            .HasConversion(id =&gt; id.Value, value =&gt; new BatchId(value))\n            .ValueGeneratedNever();\n\n        builder.Property(b =&gt; b.RecipeId)\n            .HasConversion(id =&gt; id.Value, value =&gt; new RecipeId(value));\n\n        // Collections, value objects, and domain events: next sections.\n    }\n}\n</code></pre>\n<p><code>ValueGeneratedNever</code> matters: value generation behind converters is a documented limitation area, so generate IDs in code (<code>Guid.CreateVersion7()</code> in the factory) and tell EF to keep its hands off.</p>\n<p>Notice what <code>RecipeId</code> is <em>not</em>: a <code>Recipe</code> navigation property.\nThe recipe is its own aggregate, and you don't need its grain bill to take a gravity reading.\nThe foreign key column still exists, but the domain model doesn't traverse it.</p>\n<h2>The Encapsulated Collection</h2>\n<p>By convention, EF finds the <code>_readings</code> backing field for a navigation named <code>Readings</code>, but I configure it explicitly so the mapping survives a rename:</p>\n<pre><code class=\"language-csharp\">builder.HasMany&lt;FermentationReading&gt;(&quot;_readings&quot;)\n    .WithOne()\n    .HasForeignKey(&quot;batch_id&quot;);\n\nbuilder.Navigation(&quot;_readings&quot;)\n    .UsePropertyAccessMode(PropertyAccessMode.Field)\n    .AutoInclude();\n</code></pre>\n<p><code>PropertyAccessMode.Field</code> tells EF to read and write the field and never touch the public view.\n<code>AutoInclude</code> is my default for aggregates: the <code>Bottle</code> invariant reads the readings, so a half-loaded <code>Batch</code> is unsafe to use.\n<code>WithOne()</code> with no arguments means the child has no navigation back to <code>Batch</code>; the <code>batch_id</code> foreign key lives only as a shadow property.</p>\n<h2>State With No Property at All</h2>\n<p><code>_bottledAtUtc</code> has no property, only a private field, and EF maps it anyway:</p>\n<pre><code class=\"language-csharp\">builder.Property&lt;DateTime?&gt;(&quot;_bottledAtUtc&quot;)\n    .HasColumnName(&quot;bottled_at_utc&quot;);\n</code></pre>\n<p>The cost shows up on the query side: filtering on the field means writing <code>EF.Property&lt;DateTime?&gt;(b, &quot;_bottledAtUtc&quot;)</code> in the LINQ query.\nMy rule: private setters for state that queries filter on, field-only mapping for state only the aggregate itself needs.</p>\n<pre><code class=\"language-csharp\">var bottledThisWeek = await context.Batches\n    .Where(b =&gt; EF.Property&lt;DateTime?&gt;(b, &quot;_bottledAtUtc&quot;) &gt;= weekAgo)\n    .ToListAsync();\n</code></pre>\n<h2>Value Objects: Complex Types, Owned Types, and Conversions</h2>\n<p><code>Volume</code> is a <a href=\"https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals\"><strong>value object</strong></a>, and multi-property value objects map as <a href=\"https://milanjovanovic.tech/blog/complex-types-ef-core\"><strong>complex types</strong></a>, which store their members inline in the owner's table (no join, no separate identity):</p>\n<pre><code class=\"language-csharp\">builder.ComplexProperty(b =&gt; b.Volume, volume =&gt;\n{\n    volume.Property(v =&gt; v.Amount).HasColumnName(&quot;volume_amount&quot;);\n    volume.Property(v =&gt; v.Unit).HasColumnName(&quot;volume_unit&quot;);\n});\n</code></pre>\n<p>Complex types were a v1 feature in EF Core 8 (no optional properties, no collections); EF Core 10 lifted both.\nOn EF 8 or 9, a collection of value objects (say the batch kept its <code>HopAddition</code> schedule) falls back to <a href=\"https://milanjovanovic.tech/blog/owned-types-ef-core-ddd\"><strong>owned types</strong></a>, which work everywhere but carry a hidden shadow key, because EF treats them as entities pretending to be values:</p>\n<pre><code class=\"language-csharp\">builder.OwnsMany(b =&gt; b.HopAdditions, hop =&gt;\n{\n    hop.ToTable(&quot;hop_additions&quot;);\n    hop.WithOwner().HasForeignKey(&quot;batch_id&quot;);\n});\n</code></pre>\n<p>The enum takes a plain conversion, stored as text so the database stays readable:</p>\n<pre><code class=\"language-csharp\">builder.Property(b =&gt; b.Status)\n    .HasConversion&lt;string&gt;()\n    .HasMaxLength(20);\n</code></pre>\n<p>All converted properties share one caveat: LINQ operates on the provider type, so sorting by <code>Status</code> gives alphabetical order (<code>Bottled</code>, <code>Dumped</code>, <code>Fermenting</code>) rather than lifecycle order.</p>\n<h2>Domain Events Stay Out of the Schema</h2>\n<p><code>IDomainEvent</code> is no entity, so tell EF to leave the <code>DomainEvents</code> collection alone:</p>\n<pre><code class=\"language-csharp\">builder.Ignore(b =&gt; b.DomainEvents);\n</code></pre>\n<p>The events still need to go somewhere, and a <code>SaveChangesInterceptor</code> is the natural place:</p>\n<pre><code class=\"language-csharp\">public sealed class DomainEventsInterceptor(IDomainEventsDispatcher dispatcher)\n    : SaveChangesInterceptor\n{\n    public override async ValueTask&lt;int&gt; SavedChangesAsync(\n        SaveChangesCompletedEventData eventData,\n        int result,\n        CancellationToken cancellationToken = default)\n    {\n        var domainEvents = eventData.Context!.ChangeTracker\n            .Entries&lt;Batch&gt;()\n            .SelectMany(entry =&gt;\n            {\n                var events = entry.Entity.DomainEvents.ToList();\n                entry.Entity.ClearDomainEvents();\n                return events;\n            })\n            .ToList();\n\n        await dispatcher.DispatchAsync(domainEvents, cancellationToken);\n\n        return await base.SavedChangesAsync(eventData, result, cancellationToken);\n    }\n}\n</code></pre>\n<p><code>IDomainEventsDispatcher</code> is the strongly typed dispatcher I built in <a href=\"https://milanjovanovic.tech/blog/building-a-custom-domain-events-dispatcher-in-dotnet\"><strong>building a custom domain events dispatcher</strong></a>, with no MediatR dependency; that post also covers dispatching before the save when handlers must share the transaction.</p>\n<h2>The Domain Never References EF Core</h2>\n<p>Every mapping snippet so far lives in <code>BatchConfiguration</code>, none of it in <code>Batch</code>: no mapping attributes, no ORM base class.\nThat's <strong>persistence ignorance</strong>, and the fluent configuration API is what makes it possible.\nThe <code>DbContext</code> sits in the <a href=\"https://milanjovanovic.tech/blog/infrastructure-layer-clean-architecture\"><strong>infrastructure layer</strong></a> and picks up every configuration from its own assembly:</p>\n<pre><code class=\"language-csharp\">public sealed class BreweryDbContext(DbContextOptions&lt;BreweryDbContext&gt; options)\n    : DbContext(options)\n{\n    public DbSet&lt;Batch&gt; Batches =&gt; Set&lt;Batch&gt;();\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.ApplyConfigurationsFromAssembly(\n            typeof(BreweryDbContext).Assembly);\n    }\n}\n</code></pre>\n<h2>Summary</h2>\n<p>The &quot;EF Core forces anemic models&quot; objection expired years ago.\nA private constructor, backing fields, value conversions, and complex types cover everything a fully encapsulated aggregate needs, and all of it lives in configuration classes the domain never sees.\nThe real limits come down to two: navigations can't be constructor-bound, and converted or field-only members translate through the provider type in queries.</p>\n<p>The ORM was never the thing keeping your domain model anemic.\nIt's a mapping exercise, done once per aggregate, and the encapsulation holds from then on.</p>\n<p>If you want to go deeper into modeling aggregates, value objects, and rich behavior across a real system, that's what I teach in <a href=\"https://milanjovanovic.tech/pragmatic-domain-driven-design\"><strong>Pragmatic Domain-Driven Design</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core",
            "title": "Persisting a Rich Domain Model With EF Core",
            "summary": "Every time I show a rich domain model, someone tells me EF Core can't persist it without public setters and a public constructor.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_206.png",
            "date_modified": "2026-08-08T00:00:00.000Z",
            "date_published": "2026-08-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/should-you-split-that-into-microservices-ask-these-5-questions-first",
            "content_html": "<p>Split into microservices only when you can point at concrete drivers: measurably different scaling needs, teams blocking each other in one release process, a data boundary you can draw cleanly, or a real need for independent failure or release.\nYou also have to afford the platform tax.\nWith two or three yes answers, build a modular monolith instead.</p>\n<p>I've helped teams adopt microservices, and I've helped teams dig themselves out of microservices.\nThe second group is bigger.</p>\n<p>In almost every failure case, the decision to split came before the reasons did.\nThe app might need to scale someday.\nThe monolith feels messier every sprint.\nA conference talk made independent deployments look easy.\nNone of those are reasons to take on a distributed system.</p>\n<p>Meanwhile, the actual trade is brutal and specific: <strong>microservices exchange local complexity for distributed complexity.</strong>\nA method call becomes a network hop.\nA transaction becomes a <a href=\"https://milanjovanovic.tech/blog/saga-pattern-dotnet\"><strong>saga</strong></a>.\nA stack trace becomes a distributed trace across three services and a queue.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/method_call_vs_distributed.png\" alt=\"The same feature as a method call in a monolith, versus a network call between two services with retries, an outbox, idempotency, and tracing\">\n<p>Sometimes that trade is worth it.\nI've made it myself, and I'd make it again <em>in the right situation</em>.\nThe five questions below are how I find out.\nAnswer them honestly and the decision usually makes itself.</p>\n<h2>1. Do Parts of the System Have Genuinely Different Scaling Needs?</h2>\n<p>I don't mean needs you might have someday.\nI mean needs you can measure today: one part of the system handles 100x the traffic of the rest, or needs a GPU, or eats memory in a way that forces you to size the whole deployment for its peak.</p>\n<p>That's a real reason.\nExtracting a hot path so it can scale (and fail) independently is one of the best arguments for a service boundary.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/hot_path_scaling.png\" alt=\"Three monolith instances each duplicating a hot Search module, versus one monolith instance plus three copies of an extracted Search service\">\n<p>But check the honest version first: most .NET monoliths <a href=\"https://milanjovanovic.tech/blog/scaling-monoliths-a-practical-guide-for-growing-systems\"><strong>scale out fine behind a load balancer</strong></a>.\nIf your whole app comfortably runs on three instances, you don't have a scaling problem worth a service boundary.</p>\n<h2>2. Are Teams Actually Blocking Each Other?</h2>\n<p>Microservices are an organizational tool as much as a technical one.\nThe strongest version of this signal looks like: multiple teams, one codebase, and a release process where team A's half-finished feature delays team B's hotfix.\nDeploy trains, release freezes, merge queues that take a day.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/deploy_train_vs_independent.png\" alt=\"Three teams\">\n<p>If that's your life, independent deployability has real value.</p>\n<p>If you're a team of six, it isn't.\nOne team doesn't step on itself hard enough to justify operating a distributed system.\nI'd go as far as saying: <strong>below roughly two full teams, the organizational argument for microservices is zero.</strong></p>\n<h2>3. Can You Draw the Data Boundary?</h2>\n<p>This is the question that kills most splits, and it's the one people skip.</p>\n<p>Each service must <strong>own its data</strong> outright.\nOwning it means no other service reads its tables directly, not even for one convenient join.\nIf two candidate services constantly need each other's data to answer basic queries, they aren't two services.\nThey're one service you're about to cut in half.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/data_boundary_entanglement.png\" alt=\"Two candidate services, each owning its database, with red cross-boundary queries between them: one service, cut in half\">\n<p>I learned this one the hard way, and wrote about it in <a href=\"https://milanjovanovic.tech/blog/the-modular-monolith-boundary-i-couldnt-take-back\"><strong>the modular monolith boundary I couldn't take back</strong></a>: a boundary that looks clean on the org chart can be hopelessly entangled at the data level.\nThe entanglement doesn't go away when you add a network between the halves.\nIt gets worse, because now every &quot;join&quot; is an API call, and <a href=\"https://milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith\"><strong>keeping the data boundaries intact</strong></a> becomes a distributed problem.</p>\n<h2>4. Does Anything Require Independent Failure or Release?</h2>\n<p>Some parts of a system carry requirements the rest doesn't:</p>\n<ul>\n<li>A payment flow that must stay up even when the reporting module is down</li>\n<li>A component with a compliance boundary (PCI, HIPAA) where you want the audited surface as small as possible</li>\n<li>An integration that ships weekly while the core ships quarterly</li>\n</ul>\n<p>These are legitimate isolation requirements, and a service boundary is a clean way to express them.\nNotice how specific they are.\nA general wish for isolation is not on the list.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/independent_failure_isolation.png\" alt=\"The rest of the system with Reporting down in one dashed boundary, and a healthy Payments service isolated in its own, where the PCI scope stops\">\n<h2>5. Can You Afford the Platform Tax?</h2>\n<p>Before the first microservice delivers any value, you need: a container platform, CI/CD per service, centralized logging, <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>distributed tracing</strong></a>, a message broker, and the reliability patterns that make inter-service communication safe (<a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>outbox</strong></a>, <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>idempotent consumers</strong></a>, retries with backoff).</p>\n<p>That's the entry fee, paid in engineer-months, before benefit number one.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/platform_tax_iceberg.png\" alt=\"Iceberg: one microservice above the waterline, with the container platform, CI/CD, logging, tracing, broker, and reliability patterns below it\">\n<p>A team that can't spare that capacity doesn't get a cheaper version of microservices.\nIt gets a distributed monolith with none of the benefits and all of the costs.</p>\n<h2>Scoring It</h2>\n<p>The rule I use:</p>\n<ul>\n<li><strong>Four or five yes answers:</strong> split, and start with one service, not twelve. Extract the piece with the clearest boundary and run it in production for a quarter before extracting the next.</li>\n<li><strong>Two or three:</strong> you want modules, not services. A <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a> gives you the boundaries, the team ownership, and the option to split later, without the platform tax. The boundaries you enforce now are exactly <a href=\"https://milanjovanovic.tech/blog/breaking-it-down-how-to-migrate-your-modular-monolith-to-microservices\"><strong>what makes the eventual migration mechanical</strong></a> instead of heroic.</li>\n<li><strong>Zero or one:</strong> keep the monolith and invest the energy you just saved into making it excellent.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_205/five_questions_scoring.png\" alt=\"The five questions feed one decision: how many honest yes answers. Four or five means microservices, two or three a modular monolith, zero or one keep the monolith\">\n<p>The teams that regret microservices almost never got the technology wrong.\nThey got this checklist wrong, eighteen months earlier, in the meeting where the split was decided before the reasons existed.\nRun the five questions before your version of that meeting.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/should-you-split-that-into-microservices-ask-these-5-questions-first",
            "title": "Should You Split That Into Microservices? Ask These 5 Questions First",
            "summary": "Nobody regrets microservices on day one. The regret shows up eighteen months later, when the team is drowning in distributed-systems problems they never needed…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_205.png",
            "date_modified": "2026-08-01T00:00:00.000Z",
            "date_published": "2026-08-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-system-design-behind-my-new-saas",
            "content_html": "<p>Katabench runs on three Hetzner servers inside one private VPN, with nothing public except ports 80 and 443.\nThe API publishes each submission to NATS, a worker on a separate server picks it up, and the code runs in a throwaway container sandbox with no network access.\nAuth and payments are the only pieces I buy instead of self-host.</p>\n<p>For the past few months, I've been building the most fun system of my career: one that takes C# code from strangers, compiles it, and runs it on my own servers.</p>\n<p>It's called <a href=\"https://katabench.com\"><strong>Katabench</strong></a>, a hands-on coding platform for .NET developers, and it's what came out of <a href=\"https://milanjovanovic.tech/blog/the-urge-to-build-something\"><strong>the urge to build something</strong></a> I wrote about in January.\nYou write code in the browser, across challenges for algorithms, EF Core, raw SQL, architecture tests, refactoring, secure coding, and test writing.</p>\n<p>Nothing is simulated.\nA database challenge runs against a live Postgres instance, and you get back real execution numbers: allocated memory, query speed, the SQL your code produced.</p>\n<p>But this isn't a product tour.\nThe system design is the part you can steal.\n&quot;Run untrusted code safely on hardware you own&quot; sounds like a big-company problem. It turned out to fit on three servers.</p>\n<p>Here's what we'll cover:</p>\n<ul>\n<li>The three-server layout, and what runs where</li>\n<li>Why no server is reachable from the public internet</li>\n<li>How I (safely) run untrusted code on my own machines</li>\n<li>Where I buy instead of self-host, and why</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Big Picture</h2>\n<p>Katabench is three applications: a marketing website, the app itself, and the backend.</p>\n<p>The marketing site is <a href=\"https://astro.build\"><strong>Astro</strong></a>, for the same reason this website runs on Astro: nothing beats it for static content and SEO.\nThe app is <a href=\"https://react.dev\"><strong>React</strong></a> with <a href=\"https://vite.dev\">Vite</a> and <a href=\"https://www.typescriptlang.org\">TypeScript</a>. It's a stack I'm familiar with, and you can build pretty much anything with it.\nBoth are static builds served through <a href=\"https://pages.cloudflare.com\"><strong>Cloudflare</strong></a>, which costs me exactly $0 and includes the DNS, CDN, and DDoS protection I'd otherwise be paying for.</p>\n<p>The backend is .NET, and that's where it gets interesting.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_204/katabench_system_design.png\" alt=\"The full Katabench system design: an Astro marketing site and React app served through Cloudflare, with Clerk for auth and Paddle for payments, and a backend of three Hetzner servers inside one VPN behind a Traefik reverse proxy. One server runs the .NET API with Postgres, Redis and NATS, another runs the worker and the isolated sandbox, and a third runs the Grafana observability stack.\">\n<p>Everything inside the big box runs on three <a href=\"https://www.hetzner.com/cloud\"><strong>Hetzner</strong></a> VPSs:</p>\n<ul>\n<li>The <strong>API server</strong>: the .NET API and its data stores</li>\n<li>The <strong>worker server</strong>: code execution, inside isolated sandboxes</li>\n<li>The <strong>platform server</strong>: deployments and observability</li>\n</ul>\n<p>The rest of the stack: <a href=\"https://github.com/juanfont/headscale\"><strong>Headscale</strong></a> for the VPN,\n<a href=\"https://dokploy.com\"><strong>Dokploy</strong></a> for deployments, <a href=\"https://traefik.io\"><strong>Traefik</strong></a> as the reverse proxy,\nand <a href=\"https://grafana.com\"><strong>Grafana</strong></a> for observability.\nAuth is <a href=\"https://clerk.com\"><strong>Clerk</strong></a> and payments are <a href=\"https://paddle.com\"><strong>Paddle</strong></a>; both decisions get their own section at the end.</p>\n<h2>Private by Default</h2>\n<p>The first design decision wasn't about the application at all: all of my servers are part of the same <strong>private network</strong> (VPN), and nothing is exposed to the public internet except ports 80 and 443.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_204/katabench_vpn.png\" alt=\"The Katabench VPN: the three backend servers, my desktop PC, and a Mac Mini for agent loops, all joined to one private network, with a self-hosted Headscale instance (managed through Headplane) as the control plane and ACLs restricting what each machine can reach.\">\n<p>My desktop PC is on the same VPN, and so is a Mac Mini that runs agent loops for me.\nThat's how I reach Dokploy, Grafana, and the database.\nThere's no public admin endpoint exposed.</p>\n<p>The two public ports lead to <strong>Traefik</strong>, the reverse proxy that Dokploy manages for me, and Traefik routes traffic to the API.\nEverything else (Postgres, Redis, NATS, Grafana, Dokploy's own dashboard) is reachable only over the VPN.</p>\n<p>A few weeks ago, I showed you how to <a href=\"https://milanjovanovic.tech/blog/build-your-own-vpn-with-tailscale\"><strong>build your own VPN with Tailscale</strong></a>.\nKatabench runs on the same idea, except the control plane is <strong>Headscale</strong>, an open-source, self-hosted implementation of Tailscale's coordination server, with <a href=\"https://github.com/tale/headplane\">Headplane</a> as its admin UI.\nUnderneath it's still a <a href=\"https://www.wireguard.com\">WireGuard</a> mesh. What changes is that I run the coordination server myself.</p>\n<p>Headscale's <strong>ACLs</strong> decide what each machine is allowed to reach, so the worker server can't open a connection to the database even though they share a network.\nIf Headscale goes down, existing tunnels keep working, but new machines can't join until I fix it.\nI can live with that: users never notice, and Headscale costs nothing.</p>\n<p>Why Hetzner? Price.\nThey raised their prices recently (my timing was impeccable to miss grandfathering the old prices...), but the math still beats every managed alternative.</p>\n<h2>The API Server and Everything It Leans On</h2>\n<p>The API server runs the .NET API and the <a href=\"https://www.postgresql.org\"><strong>Postgres</strong></a> database right next to it.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_204/katabench_api.png\" alt=\"The API server: a .NET API talking to its local Postgres database, a Redis key-value cache, and a NATS JetStream stream carrying submission messages out to the worker, with a local OpenTelemetry agent alongside and nightly database backups shipped to Cloudflare R2.\">\n<p>If you're used to running everything in the cloud, a database on the same box as the API looks wrong.\nHere's why I did it anyway:</p>\n<ul>\n<li>A localhost connection has no network hop, so query latency is as low as it gets.</li>\n<li>A managed Postgres alone would likely cost more than I pay for this whole server.</li>\n<li>The failure modes are already coupled: if this server dies, the API is down whether or not the database survived it.</li>\n</ul>\n<p>The real price is that backups are my problem now.\nEvery night, a cron job ships a <code>pg_dump</code> to <a href=\"https://developers.cloudflare.com/r2/\">Cloudflare R2</a>, the exact setup I showed in <a href=\"https://milanjovanovic.tech/blog/how-i-migrated-my-website-to-cloudflare-and-saved-228-dollars\"><strong>last week's issue</strong></a>.</p>\n<p>Also on this box:</p>\n<ul>\n<li><a href=\"https://redis.io\"><strong>Redis</strong></a> for caching.</li>\n<li><a href=\"https://nats.io\"><strong>NATS</strong></a> for lightweight messaging, which I use as a <a href=\"https://milanjovanovic.tech/blog/nats-jetstream-job-queue-dotnet\"><strong>job queue</strong></a> between the API and the worker. <a href=\"https://docs.nats.io/nats-concepts/jetstream\">JetStream</a> gives it durable storage, and it handles far more scale than I'll ever need.</li>\n<li>An <a href=\"https://opentelemetry.io\"><strong>OpenTelemetry</strong></a> collector running in agent mode. More on that when we get to the platform server.</li>\n</ul>\n<h2>The Worker and the Sandbox</h2>\n<p>This is the part of the system I spent the most time on, because this is where strangers run arbitrary C# on my hardware.\nEvery decision here starts from one assumption: someone will eventually try to break out.</p>\n<p>When you hit Submit, the API doesn't execute anything.\nIt publishes a message to NATS and moves on.</p>\n<p>The <strong>worker</strong>, on its own server, picks up the message. This is the classic <a href=\"https://milanjovanovic.tech/blog/how-to-scale-long-running-api-requests\"><strong>queue with competing consumers</strong></a> setup: scaling out just means adding another consumer.\nIf submissions pile up, I create another worker server, join it to the VPN, connect it to Dokploy, and it starts pulling from the same stream.</p>\n<p>The worker has no database connection string or API credential.\nIts ACL also prevents it from reaching the database, limiting what a sandbox escape could expose.</p>\n<p>And the worker doesn't run your code either.\nIt hands it to a <strong>sandbox</strong>: an isolated, throwaway environment that compiles and executes a single submission.\nA sandbox can't touch another sandbox, and it can't reach anything else in the system.\nI'll write more about the specifics of the sandbox in a future issue, but the high-level idea is that it runs inside a container with no network access and a strict resource limit.</p>\n<p>Because a sandbox can't touch anything, it's free to spin up whatever a challenge needs inside itself, like the throwaway Postgres instance your EF Core code runs against.\nThat's how Katabench hands you back the SQL your code actually executed.\nSome challenges go further and inspect the query plan to check that your solution uses the right index.</p>\n<p>It's also the basis for something bigger I'm working on: long-lived sandboxes where you build out a complete system, like a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-architecture-dotnet\"><strong>modular monolith</strong></a>, in an in-browser IDE.\nMore on that another time.</p>\n<h2>The Platform Server</h2>\n<p>The third server never serves a user request.\nIt exists to manage and observe the other two.</p>\n<p><strong>Dokploy</strong> runs here.\nIt's a self-hosted PaaS that gives me the Azure App Service experience on my own machines: push code, get a deployment, and Traefik routing is handled for me.\nThe feature that sold me is remote server management: one Dokploy instance deploys to the whole fleet.</p>\n<p>The other half of this server is the Grafana observability stack:</p>\n<ul>\n<li><strong>Grafana</strong> for dashboards</li>\n<li><a href=\"https://grafana.com/oss/loki/\"><strong>Loki</strong></a> for structured logs</li>\n<li><a href=\"https://grafana.com/oss/tempo/\"><strong>Tempo</strong></a> for distributed traces</li>\n<li><a href=\"https://prometheus.io\"><strong>Prometheus</strong></a> for metrics</li>\n</ul>\n<p>The OpenTelemetry collectors on the other two servers run in <a href=\"https://milanjovanovic.tech/blog/opentelemetry-collector-agent-gateway\"><strong>agent mode</strong></a> and stream telemetry to a gateway collector here, which feeds that stack.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_204/katabench_platform.png\" alt=\"Telemetry from the other servers arriving at the gateway OpenTelemetry collector on the platform server, which forwards it into the Grafana stack of Grafana, Loki, Tempo, and Prometheus, with Dokploy running on the same box.\">\n<p>My applications never talk to the monitoring backend directly; they hand everything to the local agent and stay decoupled from wherever the data ends up.</p>\n<p>Is a dedicated observability server overkill for a brand-new SaaS?\nI don't think so.\nWhen your system executes other people's code, &quot;what exactly happened here&quot; is not a question you want to answer by grepping docker logs across three servers.</p>\n<h2>Where I Buy Instead of Self-Host</h2>\n<p>Everything so far is self-hosted, which will surprise nobody who saw <a href=\"https://www.youtube.com/watch?v=4gEUdn5hq_U\"><strong>my side project tech stack</strong></a> video earlier this year.\nTwo pieces of Katabench went the other way: auth and payments.</p>\n<p>Auth first: I chose <strong>Clerk</strong> over <a href=\"https://www.keycloak.org\"><strong>Keycloak</strong></a>.\nI've shown you how to <a href=\"https://milanjovanovic.tech/blog/integrate-keycloak-with-aspnetcore-using-oauth-2\"><strong>integrate Keycloak with ASP.NET Core</strong></a>, and it's still what I recommend when you want full control over your identity setup.</p>\n<p>But full control means all of it is my job: running the service and its database, building the sign-in forms and emails, and wiring up every external identity provider by hand.\nClerk makes all of that someone else's job: sign-in components that drop into my React app, transactional emails, social logins that take minutes to enable, and users synced back to my backend.</p>\n<p>The tradeoff: a per-user bill as you grow, and a dependency I don't control.\nAt Katabench's current stage, getting to an MVP faster is worth more than the long-term cost of running my own auth service.</p>\n<p>Payments follow the same logic, with one extra constraint.\n<strong>Paddle</strong> is a <strong>merchant of record</strong>, meaning they are legally the seller and handle global sales tax and VAT.\nAs a solo developer selling worldwide, I'd rather give up a percentage than file tax returns in countries I couldn't place on a map.</p>\n<p>And the constraint: I run my business from Serbia, and Stripe isn't available here.\nPaddle is.</p>\n<p>The pattern behind both decisions: <strong>I'll self-host infrastructure, but not auth or payments.</strong>\nEverything else stays on my own servers, where the monthly bill stays the same no matter how much traffic shows up.</p>\n<h2>Summary</h2>\n<ul>\n<li><strong>Keep the application servers private.</strong> The VPN connects the fleet, while ACLs limit what each machine can reach.</li>\n<li><strong>Move code execution off the API server.</strong> NATS carries each submission to a worker, which hands it to an isolated, throwaway sandbox.</li>\n<li><strong>Separate the platform services.</strong> Dokploy and the observability stack run without competing with user requests.</li>\n<li><strong>Choose what not to self-host.</strong> I kept the infrastructure on my own servers and handed auth and payments to Clerk and Paddle.</li>\n</ul>\n<p>Every box in the diagram hides decisions worth an issue of its own: hardening Headscale, building the sandbox, wiring the OpenTelemetry pipeline, running a Dokploy fleet.</p>\n<p>Hit reply and tell me which part you want me to zoom in on first.\nYour questions will shape the next few issues and videos.</p>\n<p>And if you're curious how all of this feels from the other side of the sandbox, <a href=\"https://katabench.com\"><strong>Katabench</strong></a> is live.\nGive it a try and tell me what breaks.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-system-design-behind-my-new-saas",
            "title": "The System Design Behind My New SaaS",
            "summary": "I just shipped Katabench, and it's the most fun system I've ever built: it takes C# from strangers and runs it safely on servers I own.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_204.png",
            "date_modified": "2026-07-25T00:00:00.000Z",
            "date_published": "2026-07-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-i-migrated-my-website-to-cloudflare-and-saved-228-dollars",
            "content_html": "<p>I moved this site from Netlify to Cloudflare Pages without downtime by deploying to both hosts from the same GitHub Actions build, verifying the Pages copy on its own URL, then flipping a single DNS record.\nNetlify kept serving production until that flip, so rolling back meant repointing one record.\nThe new hosting bill is $0.</p>\n<p>Earlier this month, I migrated this website from Netlify to Cloudflare.\nThe site was down for zero seconds, no reader noticed a thing, and the hosting bill went from <strong>$19 a month to $0</strong>.\nThat's $228 a year, for one afternoon of work.</p>\n<p>Migrations like this have a scary reputation, so I want to show you the playbook I used:</p>\n<ul>\n<li>The dual-deploy CI setup that removes most of the risk</li>\n<li>Moving DNS without touching the website</li>\n<li>The cutover, and the one thing that broke</li>\n<li>A sidenote on R2, the hidden gem of Cloudflare's free tier</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>What $19 a Month Was Buying</h2>\n<p>This is not a Netlify hate story.\nNetlify served this site reliably for years, and the developer experience is genuinely good.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_203/netlify_bandwidth.png\" alt=\"Netlify dashboard showing the bandwidth usage for the site, which is ~280 GB per month\">\n<p>But the setup had quietly become redundant.\nThis site compiles to static files, and my <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>GitHub Actions pipeline</strong></a> already does the building.\nNetlify's job, at the end of all that, was to put a folder of files behind a CDN.</p>\n<p>Serving static files is a commodity in 2026.\n<a href=\"https://pages.cloudflare.com\"><strong>Cloudflare Pages</strong></a> does it for free, with unmetered bandwidth, on one of the largest edge networks in the world.</p>\n<p>The hard part is switching without breaking the wiring a website accumulates over the years: DNS records, email authentication, redirects, comment auth.</p>\n<h2>The Plan</h2>\n<p>The whole migration hangs on one principle:</p>\n<p><strong>The old host keeps serving production until a single DNS change flips traffic to the new one.</strong></p>\n<p>Everything else is preparation that can't affect the live site:</p>\n<ol>\n<li>Deploy to Cloudflare Pages <em>in parallel</em>, and verify the copy on its own URL.</li>\n<li>Move DNS hosting to Cloudflare while the records still point at Netlify.</li>\n<li>Flip the website record to Pages.</li>\n<li>Let it bake, then decommission Netlify.</li>\n</ol>\n<p>At every point, a working version of the site is one DNS record away.</p>\n<h2>Step 1: One Build, Two Hosts</h2>\n<p>Cloudflare Pages can clone and build your repo itself, but I skipped that option.\nMy build already runs in GitHub Actions, with environment variables and secrets wired up there, and I didn't want to move the build and the hosting in the same step.</p>\n<p>So CI keeps building, and <a href=\"https://developers.cloudflare.com/workers/wrangler/\">wrangler</a> (Cloudflare's CLI) uploads the finished <code>dist/</code> folder.\nCloudflare calls this a <strong>direct upload</strong> deployment, and it turned the migration into one extra step in the workflow:</p>\n<pre><code class=\"language-yaml\">- name: Build 🏗\n  run: npm run build\n\n- name: Deploy to Netlify 💫\n  run: npx netlify deploy --prod --dir=dist\n\n# Temporary: dual-deploy during the migration so the Pages copy\n# can be verified against live Netlify. Delete after the cutover.\n- name: Deploy to Cloudflare Pages 💫\n  run: npx wrangler@4 pages deploy dist --project-name=mjtech --branch=main\n</code></pre>\n<p>Readers kept hitting Netlify.\nMeanwhile, I had a complete, always-current copy of production at <code>mjtech.pages.dev</code> to click through: the blog, search, course pages, the RSS feed, redirects.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_203/dual_deploy.png\" alt=\"GitHub Actions builds the site once and deploys the same dist folder to two hosts: Netlify, which keeps serving readers, and Cloudflare Pages, which hosts a verification copy at mjtech.pages.dev\">\n<p>Two GitHub secrets make it work (<code>CLOUDFLARE_API_TOKEN</code> and <code>CLOUDFLARE_ACCOUNT_ID</code>), and the first deploy creates the Pages project.\nIf your site has auth, add the new domain to your provider's authorized list (Firebase, in my case), or sign-ins will fail only on the new host.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_203/cf_pages_deployments.png\" alt=\"Cloudflare Pages deployments list showing production deployments from main and per-branch preview deployments with their own pages.dev URLs\">\n<h2>Step 2: Move DNS Without Moving the Website</h2>\n<p>The domain never left my registrar.\nOnly the <strong>nameservers</strong> changed, which hands DNS hosting to Cloudflare.</p>\n<p>Before touching anything, I inventoried every DNS record.\nThe website records are the easy ones: if they're wrong, you notice within seconds.\nThe dangerous ones are the MX records for email, the SPF, DKIM, and DMARC TXT records, domain verifications, and the newsletter sending domain.\nBreak a DKIM record, and your emails quietly start landing in spam folders until someone tells you.</p>\n<p>Cloudflare imports your existing records when you add the domain, but the import isn't guaranteed to be complete.\nIn fact, in my case it missed a few important DNS records, so I had to add them manually.\nCheck it against your inventory, record by record, and keep email and verification records <strong>DNS only</strong> (the grey cloud).</p>\n<p>Then the nameserver change at the registrar.\nThe imported records still pointed the domain at Netlify, so DNS hosting moved to Cloudflare while the live site kept serving from Netlify, unaffected.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_203/dns_cutover.png\" alt=\"Before the cutover, Cloudflare DNS still routes visitors to Netlify while the verified Pages copy waits; after the cutover, one record change routes visitors to Cloudflare Pages, and rolling back means repointing that record at Netlify, which still holds the last deploy\">\n<h2>Step 3: The Cutover</h2>\n<p>With DNS on Cloudflare and the Pages copy verified, the migration moment itself is one click: add your domain as a <strong>custom domain</strong> on the Pages project.\nCloudflare repoints the DNS record and provisions a certificate, and the moment it activates, your traffic is served by Cloudflare.\n(I used the occasion to make the apex domain the canonical host, with <code>www</code> redirecting to it.)</p>\n<p>The rollback is what makes this step safe.\nProxied DNS changes on Cloudflare take effect in seconds, and Netlify still held the latest deploy thanks to the dual-deploy step.\nIf anything had looked wrong, repointing one record would have brought the old setup back within a minute.</p>\n<p>Cutover day was July 6.\nThe dual-deploy step went into CI at 3:43 PM.\nAt 6:47 PM, traffic was on Cloudflare and the Netlify step was deleted from the pipeline.\nThree hours, most of them spent double-checking.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_203/cf_custom_domains.png\" alt=\"The Pages project\">\n<h2>The One Thing That Broke</h2>\n<p>Every migration has one.</p>\n<p>My <code>netlify.toml</code> contained a redirect from <code>/sitemap.xml</code> to <code>/sitemap-index.xml</code> (the generator produces the latter, search engines ask for the former).\nI ported the redirect rules I remembered, and this one, added years ago, wasn't in the group.\nIt 404'd after the cutover.</p>\n<p>Cloudflare Pages reads redirects from a <code>_redirects</code> file in your build output, so the fix was one line:</p>\n<pre><code class=\"language-text\">/sitemap.xml    /sitemap-index.xml    301\n</code></pre>\n<p>Before you decommission a host, read its config file line by line and ask where each rule lives now.</p>\n<h2>What the Free Tier Gets You</h2>\n<p>The $0 bill would have justified the move on its own, but it came with upgrades:</p>\n<ul>\n<li><strong>Preview deployments for every branch.</strong> Any push deploys to <code>&lt;branch&gt;.mjtech.pages.dev</code>. The issue you're reading was proofread on one of those URLs.</li>\n<li><strong>No bandwidth math.</strong> Cloudflare doesn't meter requests or bandwidth for static assets, so I no longer think about traffic spikes.</li>\n<li><strong>The proxy layer.</strong> The site sits behind Cloudflare's edge: universal SSL, HTTP/3, caching, DDoS protection, and redirect rules that run before a request reaches the origin (that's where my <code>www</code> to apex redirect lives).</li>\n<li><strong>Instant DNS.</strong> Proxied record changes propagate in seconds, which is what made the rollback plan credible.</li>\n</ul>\n<p>The trade-off is more eggs in one basket: Cloudflare is now my DNS, CDN, and host.\nAnd Pages' own build system is weaker than Netlify's, which I sidestep by building in GitHub Actions and treating Pages as dumb file hosting.</p>\n<h2>Sidenote: R2 for Database Backups</h2>\n<p>While I was in the Cloudflare dashboard anyway, I gave <a href=\"https://developers.cloudflare.com/r2/\"><strong>R2</strong></a>, their S3-compatible object storage, a real job.</p>\n<p>The free tier includes 10 GB of storage and, unique among the big clouds, <strong>zero egress fees</strong>.\nDownloading your data costs nothing, which is exactly the property you want for backups.</p>\n<p>I use it for <a href=\"https://katabench.com\"><strong>Katabench</strong></a>, the coding platform I'm building.\nEvery night, a cron job on the <a href=\"https://milanjovanovic.tech/blog/build-your-own-vpn-with-tailscale\"><strong>server</strong></a> dumps the production Postgres database and ships it to an R2 bucket with <a href=\"https://rclone.org\">rclone</a>:</p>\n<pre><code class=\"language-bash\">stamp=$(date -u +%Y%m%dT%H%M%SZ)\n\n# Dump, ship to R2, prune to 30 days of nightlies\ndocker exec appdb pg_dump -Fc -U postgres coderunner &gt; &quot;appdb-${stamp}.dump&quot;\nrclone copyto &quot;appdb-${stamp}.dump&quot; &quot;r2:backups/prod/appdb-${stamp}.dump&quot;\nrclone delete --min-age 30d r2:backups/prod/\n</code></pre>\n<p>Thirty days of nightly backups, off the server, on the free tier.\nTo rclone, R2 is just S3 with a Cloudflare endpoint.</p>\n<p>If you self-host anything, don't skip this piece.\nA backup on the same box as the database has the same blast radius as the database.</p>\n<h2>Summary</h2>\n<p>The playbook, in five lines:</p>\n<ol>\n<li><strong>Deploy to both hosts from the same CI build</strong>, and verify the new copy on its own URL while the old one serves production.</li>\n<li><strong>Move DNS first, cut over second.</strong> Nameservers can change hands while every record still points at the old host.</li>\n<li><strong>Inventory your DNS records before the move.</strong> The email records are the ones that hurt.</li>\n<li><strong>Read the old platform's config before deleting it.</strong> Redirects hide in there.</li>\n<li><strong>Keep the old host warm until you're sure.</strong> Rollback should be a single DNS record.</li>\n</ol>\n<p>Total cost: one afternoon.\nTotal savings: $228 a year, plus branch previews and free off-site backups.</p>\n<p>The best migrations are the ones nobody notices.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-i-migrated-my-website-to-cloudflare-and-saved-228-dollars",
            "title": "How I Migrated My Website to Cloudflare and Saved $228",
            "summary": "This website ran on Netlify for years, at $19 a month. Earlier this month I moved it to Cloudflare Pages: same repo, same GitHub Actions pipeline, zero…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_203.png",
            "date_modified": "2026-07-18T00:00:00.000Z",
            "date_published": "2026-07-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-test-vertical-slice-architecture",
            "content_html": "<p>Test a slice through its public entrance: a real HTTP request in, and assertions on the response plus the rows it wrote.\n<code>WebApplicationFactory</code> hosts the app in-memory while Testcontainers gives you a real Postgres in Docker, both started once per test class.\nAnything you own, including the database, stays real in the test.</p>\n<p>Whenever I write about <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>vertical slice architecture</strong></a>, the same question shows up in my inbox: <strong>&quot;OK, but how do I test it?&quot;</strong></p>\n<p>It's a fair question, because the testing habits most of us learned grew up alongside layered architecture.\nMock the repository, test the service, assert the service called the repository.\nIn VSA there is no service layer to test and often no repository to mock.\nThe slice is a request, a handler, and the database work, living together in one place.</p>\n<p>People read that and conclude vertical slices are hard to test.\nIt's the opposite.\nYou just have to stop testing layers and start testing what the slice actually is: <strong>a feature</strong>.</p>\n<h2>The Slice Is the Unit</h2>\n<p>A vertical slice has a natural contract: a request goes in at the top, and an observable outcome comes out the bottom (a response, plus rows in a database, plus maybe a message on a bus).</p>\n<p>So test exactly that contract:</p>\n<p><strong>One slice = one focused set of tests that exercise it from the endpoint to the database.</strong></p>\n<p>Not a handler test with a mocked <code>DbContext</code>.\nNot an endpoint test with a mocked handler.\nThe whole slice, through its public entrance, against a real database.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_202/slice_test_boundary.png\" alt=\"A test boundary drawn around an entire vertical slice: the test sends an HTTP request through the endpoint and handler to a real Postgres container and asserts on the response and the database state, with only truly external services faked\">\n<p>If you're coming from layered architecture, this feels like &quot;just integration tests&quot;.\nIt is, and that's the point: <a href=\"https://milanjovanovic.tech/blog/the-test-pyramid-is-a-lie-and-what-i-do-instead\"><strong>the test pyramid is a lie</strong></a> for this kind of code.\nA slice test catches broken SQL, broken mapping, broken validation, and broken routing in one go, none of which a mocked-out unit test can see.</p>\n<h2>The Setup That Makes It Practical</h2>\n<p>Two pieces make slice tests fast enough to run constantly: <code>WebApplicationFactory</code> to host the app in-memory, and <a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>Testcontainers</strong></a> for real infrastructure in Docker.\nMy examples use Postgres, but the same approach covers Redis, a message broker, or anything else the slice touches.</p>\n<p>The shared fixture boots both once for the whole test class:</p>\n<pre><code class=\"language-csharp\">public class ApiFixture : WebApplicationFactory&lt;Program&gt;, IAsyncLifetime\n{\n    private readonly PostgreSqlContainer _db = new PostgreSqlBuilder()\n        .WithImage(&quot;postgres:17-alpine&quot;)\n        .Build();\n\n    protected override void ConfigureWebHost(IWebHostBuilder builder)\n    {\n        builder.UseSetting(&quot;ConnectionStrings:Database&quot;, _db.GetConnectionString());\n\n        // Fake only what you don't own (payment gateways, email providers)\n        builder.ConfigureTestServices(services =&gt;\n            services.AddSingleton&lt;IEmailSender, FakeEmailSender&gt;());\n    }\n\n    public async Task InitializeAsync()\n    {\n        await _db.StartAsync();\n\n        // Create the schema once the container is up. If your app already\n        // migrates on startup, drop this and let the host do it.\n        using var scope = Services.CreateScope();\n        await scope.ServiceProvider\n            .GetRequiredService&lt;AppDbContext&gt;()\n            .Database.MigrateAsync();\n    }\n\n    public new Task DisposeAsync() =&gt; _db.DisposeAsync().AsTask();\n}\n</code></pre>\n<p>And a slice test reads like a description of the feature:</p>\n<pre><code class=\"language-csharp\">using AwesomeAssertions;\n\npublic class CreateShipmentTests(ApiFixture api) : IClassFixture&lt;ApiFixture&gt;\n{\n    [Fact]\n    public async Task Creates_shipment_and_persists_it()\n    {\n        var client = api.CreateClient();\n        var orderId = Guid.NewGuid();\n\n        var response = await client.PostAsJsonAsync(&quot;/shipments&quot;, new\n        {\n            OrderId = orderId,\n            Address = &quot;123 Main Street&quot;\n        });\n\n        response.StatusCode.Should().Be(HttpStatusCode.Created);\n\n        // A fresh scope, so we read what actually persisted.\n        using var scope = api.Services.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\n        var shipment = await db.Shipments.SingleAsync(s =&gt; s.OrderId == orderId);\n        shipment.Status.Should().Be(ShipmentStatus.Pending);\n    }\n\n    [Fact]\n    public async Task Rejects_a_shipment_without_an_address()\n    {\n        var client = api.CreateClient();\n        var orderId = Guid.NewGuid();\n\n        var response = await client.PostAsJsonAsync(&quot;/shipments&quot;,\n            new { OrderId = orderId, Address = &quot;&quot; });\n\n        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);\n\n        // The 400 is only half the contract; make sure nothing slipped through.\n        using var scope = api.Services.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\n        (await db.Shipments.AnyAsync(s =&gt; s.OrderId == orderId)).Should().BeFalse();\n    }\n}\n</code></pre>\n<p>Two small choices in the assertion carry weight.\nIt reads through a <strong>fresh scope</strong>, so you see what actually persisted, not what EF Core's change tracker still holds in memory.\nAnd it filters by the <code>OrderId</code> you sent instead of grabbing &quot;the one row&quot;, which keeps the test truthful even when it isn't the only thing writing to the database.</p>\n<p>Notice what the test doesn't know: whether the slice uses MediatR or plain endpoints, EF Core or Dapper, one file or three.\nThat ignorance is the payoff.\n<strong>You can refactor everything inside the slice without touching a single test.</strong>\nTests coupled to layers punish refactoring; tests coupled to behavior enable it.</p>\n<p>A note on speed, because it's the usual objection: the container and the host start once per test class, not per test.\nOn my machine a suite like this runs in seconds, and my <a href=\"https://milanjovanovic.tech/blog/testcontainers-best-practices-dotnet-integration-testing\"><strong>Testcontainers best practices</strong></a> cover the reuse tricks that keep it that way as the suite grows.</p>\n<p>That database is shared, though, and it's worth saying out loud: every test in the class writes to the same Postgres, and the state carries over.\nFiltering by <code>OrderId</code> (like above) keeps individual assertions honest, but anything that counts rows or checks ordering wants a clean slate.\nA small helper on the fixture wipes the tables between runs, no extra libraries required:</p>\n<pre><code class=\"language-csharp\">public async Task ResetAsync()\n{\n    using var scope = Services.CreateScope();\n    var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\n\n    // Add each table the slice touches.\n    await db.Shipments.ExecuteDeleteAsync();\n}\n</code></pre>\n<p>Then call it at the top of each test.\nxUnit runs the tests in a class sequentially, so a reset at the start hands each one a clean slate:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Creates_shipment_and_persists_it()\n{\n    await api.ResetAsync();\n\n    // ... arrange, act, assert\n}\n</code></pre>\n<p>Now each test starts from a known state.</p>\n<h2>Where Unit Tests Still Earn Their Keep</h2>\n<p>Most slices are thin: validate, load, mutate, save.\nSlice tests cover those completely, and unit testing a thin handler through mocks just restates the implementation.</p>\n<p>But some slices contain real logic: pricing rules, state machines, date math around business calendars.\nWhen that happens, don't test the logic through HTTP.\nExtract it into the domain (a method on the entity, a domain service, a plain class) and unit test it there, exhaustively, with no infrastructure in sight.</p>\n<p>The split is clean:</p>\n<ul>\n<li><strong>Slice tests</strong> prove the feature works end to end: routing, validation, persistence, the happy path, and the important sad paths.</li>\n<li><a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>Unit tests</strong></a> hammer the interesting domain logic with every edge case, at nanosecond speed.</li>\n</ul>\n<p>If a slice has no interesting logic, it gets no unit tests.</p>\n<h2>Keep the Slices From Growing Into Each Other</h2>\n<p>One more failure mode: tests pass, features work, and six months later every slice quietly references three others.\nSlice independence is the property that makes VSA worth having, so put it under test too, with <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>architecture tests</strong></a>:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void Slices_should_not_reference_other_slices()\n{\n    var result = Types.InAssembly(typeof(Program).Assembly)\n        .That().ResideInNamespace(&quot;Features.Shipments&quot;)\n        .ShouldNot().HaveDependencyOn(&quot;Features.Invoicing&quot;)\n        .GetResult();\n\n    result.IsSuccessful.Should().BeTrue();\n}\n</code></pre>\n<p>Cheap to write, and it turns &quot;please don't couple slices&quot; from a code-review plea into a failing build.</p>\n<h2>Summary</h2>\n<p>Testing vertical slice architecture stops being confusing the moment you pick the right unit:</p>\n<ol>\n<li><strong>Test the slice as a feature</strong>: real HTTP in, real database out, via <code>WebApplicationFactory</code> + Testcontainers.</li>\n<li><strong>Fake only what you don't own.</strong> Your database is yours; a real one goes in the test.</li>\n<li><strong>Unit test extracted domain logic</strong>, not thin handlers.</li>\n<li><strong>Architecture tests</strong> keep slices independent while the codebase grows.</li>\n</ol>\n<p>The result is a suite that describes features instead of layers, survives refactoring, and catches the bugs that actually reach production.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-test-vertical-slice-architecture",
            "title": "How to Test Vertical Slice Architecture",
            "summary": "The most common question I get about vertical slice architecture isn't about structure. It's \"where do my tests go?\" The layered-architecture testing habits…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_202.png",
            "date_modified": "2026-07-11T00:00:00.000Z",
            "date_published": "2026-07-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/build-your-own-vpn-with-tailscale",
            "content_html": "<p>Tailscale connects your own machines into a single private, encrypted network called a tailnet, built on WireGuard.\nEvery device dials outward only, so you can bind Postgres or Grafana to the machine's tailnet IP and close every inbound firewall port.\nPrivate services need no TLS certificates or reverse proxies, because WireGuard already encrypts every byte.</p>\n<p>Right now, a bot is scanning the internet for your database.\nNot yours in particular. Every database, on every server with a public IP, around the clock.\nThe moment something on your VPS gets a public port, you're in that game whether you meant to join or not.</p>\n<p>And most of what runs on a server was never built to play it.\nThe database.\nThe admin panel.\nThe metrics dashboard.\nThe internal API only your other services call.</p>\n<p>None of that is meant for the public.\nYet the standard tutorial hands each one a public port anyway, and with it a second job: certificates, login pages, IP allowlists, and bots probing around the clock.</p>\n<p>There's a better default: keep those services private, reachable only by you and by each other, and expose nothing.\nThis issue shows you how, using <a href=\"https://tailscale.com\"><strong>Tailscale</strong></a>.</p>\n<h2>What Tailscale Actually Is</h2>\n<p>A <strong>VPN</strong> (virtual private network) is an encrypted tunnel between machines over the public internet.\nTraffic inside it stays private, even across networks you don't control.</p>\n<p><strong>Tailscale</strong> uses that idea to connect <em>your own</em> machines (PCs, servers, phones) into a single private network only your devices can see, called a <strong>tailnet</strong>.\nIt runs on <a href=\"https://www.wireguard.com/\"><strong>WireGuard</strong></a>, a modern, heavily audited encryption protocol, and manages all the keys and addresses for you.</p>\n<p>The shape is what sets it apart from an old-school VPN.\nA traditional VPN is <strong>hub and spoke</strong>: every device dials one central server, and all traffic funnels through it.\nTailscale builds a <strong>mesh</strong>, where every device connects <em>directly</em> to every other device over its own encrypted tunnel.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_201/hub_and_spoke_vs_mesh.png\" alt=\"A traditional hub-and-spoke VPN where all traffic flows through one central server, next to a Tailscale mesh where every device has a direct encrypted tunnel to every other device\">\n<p>No server sits in the traffic path.\nYour app-to-database traffic takes the shortest route between the boxes, and there's nothing to babysit.</p>\n<p>How do devices find each other with no server in the middle?\nTailscale splits the job: a <strong>coordination server</strong> keeps a directory of your devices and their keys (a phone book), while your <strong>actual data</strong> flows directly between devices, encrypted end to end.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_201/coordination_vs_data_plane.png\" alt=\"Tailscale\">\n<p>The key property: devices only ever dial <em>outward</em>, to the coordination server and to each other.\nNothing has to connect <em>inward</em>, which is what lets us close every firewall port next.</p>\n<h2>Connecting Two VPSs With Zero Open Ports</h2>\n<p>Installing Tailscale is two commands per machine (plus the app on your PC):</p>\n<pre><code class=\"language-bash\">curl -fsSL https://tailscale.com/install.sh | sh\nsudo tailscale up\n</code></pre>\n<p>Open the login URL it prints, approve the device, and it joins your tailnet with a <strong>stable private IP</strong> (in the <code>100.x.y.z</code> range) and a name that never change, on any network.</p>\n<p>Here's the setup we'll build:</p>\n<ul>\n<li><code>vps-app</code> (<code>100.64.0.2</code>) runs a .NET API in <a href=\"https://www.docker.com\">Docker</a>.</li>\n<li><code>vps-data</code> (<code>100.64.0.3</code>) runs <a href=\"https://www.postgresql.org\">Postgres</a> and <a href=\"https://grafana.com\">Grafana</a> in Docker.</li>\n<li>The API queries Postgres across the boxes, you reach everything from your PC, and the internet sees none of it.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_201/private_apps_over_tailnet.png\" alt=\"A PC, an API VPS, and a data VPS connected in one tailnet: the PC calls the API by tailnet name, the API reaches Postgres on the other VPS by tailnet IP, and a public internet node is blocked with no inbound ports open\">\n<p><strong>Close the firewall.</strong>\nBecause Tailscale only dials outward, your cloud firewall needs no inbound rules to reach these boxes.\nConfirm SSH works over the tailnet, then delete the public port-22 rule so SSH stops existing as far as the internet is concerned.</p>\n<p><strong>Bind services to the tailnet IP.</strong>\nThis is the step that makes &quot;zero open ports&quot; true.\nPublishing a Docker port the usual way (<code>-p 5432:5432</code>) binds it to <code>0.0.0.0</code>, every interface.\nPublish private services on the <strong>tailnet IP only</strong> instead. On <code>vps-data</code>:</p>\n<pre><code class=\"language-yaml\">services:\n  postgres:\n    image: postgres:18\n    restart: unless-stopped\n    environment:\n      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}\n    ports:\n      - '100.64.0.3:5432:5432'   # tailnet IP, not 0.0.0.0\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n\n  grafana:\n    image: grafana/grafana:12.1.0\n    restart: unless-stopped\n    ports:\n      - '100.64.0.3:3000:3000'\n    volumes:\n      - grafana:/var/lib/grafana\n\nvolumes:\n  pgdata:\n  grafana:\n</code></pre>\n<p>Now Postgres has no public endpoint at any layer, yet every device on your tailnet reaches it.</p>\n<p><strong>Wire the app to the other box</strong> with an ordinary connection string pointed at the data box's stable tailnet IP:</p>\n<pre><code class=\"language-yaml\">services:\n  api:\n    image: ghcr.io/milanjovanovic/api:latest\n    restart: unless-stopped\n    ports:\n      - '100.64.0.2:8080:8080'\n    environment:\n      ConnectionStrings__AppDb: 'Host=100.64.0.3;Port=5432;Database=app;Username=app;Password=${POSTGRES_PASSWORD}'\n</code></pre>\n<p>Then from your PC, on any network:</p>\n<pre><code class=\"language-bash\">curl http://vps-app:8080/health\npsql -h vps-data -p 5432 -U app app\n</code></pre>\n<p>Look back at everything this setup let you skip.\nWireGuard encrypts every byte, so TLS certificates never entered the picture.\nGrafana went up without a reverse proxy or a login page, and you reached each service by its tailnet name instead of a DNS record.\nThe services run, but the internet can't see them.</p>\n<h2>The Payoff</h2>\n<p>Once your machines share one private network, every internal service (databases, queues, dashboards, admin panels, service-to-service APIs) stops being a public endpoint you have to defend.\nIt becomes a private address you simply connect to.</p>\n<p>This is the exact pattern I run for <a href=\"https://katabench.com\"><strong>Katabench</strong></a>, the coding platform I'm building.\nOne reverse proxy on 80 and 443 is public, because users load the app through it.\nEverything else (the <strong>deployment panel</strong>, Postgres, the message queue, Grafana, all the telemetry) lives on the tailnet with no public hostname at all.</p>\n<p>The mental model that makes it stick: <strong>a public hostname is something a service has to earn</strong>, and only when an outside party genuinely must reach it.\nEverything else stays private by default.</p>\n<p>Fifteen minutes of setup, zero open ports, and your infrastructure disappears from the public internet without losing an ounce of convenience.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/build-your-own-vpn-with-tailscale",
            "title": "Build Your Own VPN With Tailscale",
            "summary": "Most of what runs on your servers was never meant to be public: databases, dashboards, admin panels, internal APIs.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_201.png",
            "date_modified": "2026-07-04T00:00:00.000Z",
            "date_published": "2026-07-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/getting-started-with-nats-jetstream-in-dotnet",
            "content_html": "<p>NATS is a messaging system that runs as a single Go binary, and JetStream is the persistence layer that turns a subject into a durable queue with at-least-once delivery.\nIn .NET you add the <code>NATS.Net</code> client, publish from an endpoint, and consume in a <code>BackgroundService</code>.\nThe handler has to be idempotent, because a crash before the ack causes a redelivery.</p>\n<p>When .NET developers need a message queue, they reach for <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>RabbitMQ</strong></a>, <a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\"><strong>Azure Service Bus</strong></a>, or a Postgres table.</p>\n<p>NATS almost never comes up.\nThat's a shame: it's quietly become one of my favorite tools for this.</p>\n<p>NATS is a messaging system written in Go that runs as a single binary with no external dependencies.\n<strong>JetStream</strong>, its durable layer, turns it into a real queue with at-least-once delivery.\nAnd the <a href=\"https://github.com/nats-io/nats.net\"><strong>.NET client</strong></a> is a pleasure to work with.</p>\n<h2>Core NATS vs JetStream</h2>\n<p>NATS has two layers, and the difference matters.</p>\n<p><strong>Core <a href=\"https://nats.io\">NATS</a></strong> is fire-and-forget pub/sub.\nYou publish to a subject, and whoever is subscribed at that moment gets it.\nIf no one is listening, the message is gone, which suits live notifications but not a work queue.</p>\n<p><strong><a href=\"https://docs.nats.io/nats-concepts/jetstream\">JetStream</a></strong> is the persistence layer on top.\nIt captures messages published to a subject into a stream on disk, so a consumer can read them later, even after a restart.\nThat persistence is what turns a subject into a durable queue.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_200/core_nats_vs_jetstream.png\" alt=\"Core NATS drops a message when no subscriber is online; JetStream persists it to a file-backed stream and delivers it later\">\n<h2>Why It's Worth a Look</h2>\n<p>A few things stood out coming from the usual brokers:</p>\n<ul>\n<li><strong>Tiny.</strong> The official server image is about <strong>18 MB</strong>, a single Go binary with no ZooKeeper or Erlang to babysit.</li>\n<li><strong>Fast.</strong> Core NATS pushes <strong>millions</strong> of small messages per second on a single node.\nJetStream adds disk persistence, so it's slower, but still comfortably in the <strong>hundreds of thousands</strong> per second.</li>\n<li><strong>Cheap to run.</strong> A server idles in tens of megabytes of RAM, so it runs right next to your app.</li>\n<li><strong>Flexible per stream.</strong> Each <a href=\"https://docs.nats.io/nats-concepts/jetstream/streams\">stream</a> sets its own storage and retention, so one server can host a cache and a strict work queue side by side.</li>\n</ul>\n<h2>Set It Up</h2>\n<p>You need the server and two NuGet packages.</p>\n<p>Run the server with JetStream enabled.\n<code>-js</code> turns it on, and <code>-sd</code> points it at a directory so streams survive a restart:</p>\n<pre><code class=\"language-yaml\"># docker-compose.yml\nnats:\n  image: nats:2.14-alpine\n  command: ['-js', '-sd', '/data']\n  ports: ['4222:4222']\n  volumes:\n    - nats-data:/data\n  restart: unless-stopped\n</code></pre>\n<p>Add the client and its dependency-injection integration:</p>\n<pre><code class=\"language-bash\">dotnet add package NATS.Net\ndotnet add package NATS.Extensions.Microsoft.DependencyInjection\n</code></pre>\n<p>Then wire it into <code>Program.cs</code>.\n<code>AddNatsClient</code> registers one multiplexed, self-reconnecting connection, and the next line exposes a JetStream context to inject anywhere:</p>\n<pre><code class=\"language-csharp\">// Program.cs\nbuilder.Services.AddNatsClient(nats =&gt;\n    nats.ConfigureOptions(opts =&gt; opts with { Url = &quot;nats://localhost:4222&quot; }));\n\nbuilder.Services.AddSingleton(sp =&gt;\n    sp.GetRequiredService&lt;INatsConnection&gt;().CreateJetStreamContext());\n</code></pre>\n<h2>Publish a Job</h2>\n<p>With the JetStream context in DI, a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint publishes in one call.\n<code>Job</code> is a plain record, and <code>NATS.Net</code> serializes it to JSON for you, so you work with typed messages, no extra setup.\n<code>EnsureSuccess</code> throws if the stream didn't store the message:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/jobs&quot;, async (CreateJob request, INatsJSContext js, CancellationToken ct) =&gt;\n{\n    var job = new Job(Guid.NewGuid(), request.Payload);\n\n    PubAckResponse ack = await js.PublishAsync(&quot;jobs.work&quot;, job, cancellationToken: ct);\n    ack.EnsureSuccess();\n\n    return Results.Accepted($&quot;/jobs/{job.Id}&quot;);\n});\n</code></pre>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_200/nats_job_pipeline.png\" alt=\"A producer publishes to a work-queue stream, and a pool of workers competes on one durable pull consumer\">\n<h2>Process Jobs in a Worker</h2>\n<p>A <code>BackgroundService</code> is the natural home for the consumer.\nIt creates the stream and durable consumer on startup, then pulls messages in a loop.\nEvery running instance shares the <code>workers</code> consumer, so they compete for jobs and each runs once:</p>\n<pre><code class=\"language-csharp\">public class JobWorker(INatsJSContext js) : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken ct)\n    {\n        await js.CreateStreamAsync(new StreamConfig(&quot;JOBS&quot;, [&quot;jobs.work&quot;])\n        {\n            Retention = StreamConfigRetention.Workqueue, // a queue: acked messages are removed\n            Storage   = StreamConfigStorage.File         // durable: survives a restart\n        }, ct);\n\n        var consumer = await js.CreateOrUpdateConsumerAsync(&quot;JOBS&quot;, new ConsumerConfig(&quot;workers&quot;)\n        {\n            AckPolicy  = ConsumerConfigAckPolicy.Explicit,\n            AckWait    = TimeSpan.FromSeconds(30), // must exceed your worst-case processing time\n            MaxDeliver = 5                         // drop a poison message after 5 tries\n        }, ct);\n\n        await foreach (var msg in consumer.ConsumeAsync&lt;Job&gt;(cancellationToken: ct))\n        {\n            await ProcessAsync(msg.Data, ct);          // the side effect\n            await msg.AckAsync(cancellationToken: ct); // then ack\n        }\n    }\n}\n</code></pre>\n<p>Register it with <code>builder.Services.AddHostedService&lt;JobWorker&gt;()</code>.\nThe worker is a singleton, so resolve scoped dependencies like <code>DbContext</code> through <code>IServiceScopeFactory</code>.</p>\n<p>Two stream settings shape how the queue behaves.</p>\n<p><code>Storage</code> is <code>File</code> (on disk, survives restarts) or <code>Memory</code> (faster, but gone on restart).</p>\n<p><code>Retention</code> controls when a message leaves the stream:</p>\n<ul>\n<li><code>Limits</code> (the default) keeps every message until it hits an age, size, or count limit. The stream is a replayable log, and reading a message doesn't remove it.</li>\n<li><code>Workqueue</code> drops a message the moment a consumer acks it, so the stream itself is the queue. Messages are delivered in publish order, oldest first (FIFO).</li>\n<li><code>Interest</code> keeps a message only while a consumer still needs it, then drops it once every interested consumer acks.</li>\n</ul>\n<p>For a job queue: <code>Workqueue</code> on <code>File</code>, as in the worker above.</p>\n<h2>Acknowledge After the Side Effect</h2>\n<p>Look closely at the worker loop: it processes first, then acks.\nThat order is the rule that makes JetStream reliable, and most quickstarts skip it.</p>\n<p><strong>Acknowledge the message <em>after</em> the side effect, never before.</strong></p>\n<p>JetStream gives you at-least-once delivery.\nIf a worker runs a job and crashes before acking, JetStream redelivers it.\nBut ack before the work is finished, and a crash leaves the job marked done with nothing to show for it.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_200/ack_after_side_effect.png\" alt=\"A worker fetches a job, runs it, persists the result, and only then acks; a crash before the ack causes a redelivery\">\n<p>The flip side is that a job can run more than once, so your handler has to be idempotent.\nThe usual fix is to track the messages you've already handled and skip duplicates, in the same transaction as the side effect.\nI covered the full pattern in <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>The Idempotent Consumer Pattern in .NET</strong></a>.\nAt-least-once delivery only holds up when the handler reading the stream is idempotent.</p>\n<h2>Summary</h2>\n<p>NATS JetStream gives you a durable, at-least-once work queue from a single 18 MB binary, and it slots into an ASP.NET Core app cleanly: publish from an endpoint, process in a <code>BackgroundService</code>, ack after the work is done.</p>\n<p>I went in skeptical, half-expecting to miss RabbitMQ.\nIt won me over: easy to operate, no surprises, and it clusters with Raft-based replication when a bigger load calls for it.\nIt's now the first thing I reach for when I need a queue and don't want to think much about the broker.\nIt runs my own production job queue today, and I wrote up that design (two streams, competing consumers, publish-before-ack) in <a href=\"https://milanjovanovic.tech/blog/nats-jetstream-job-queue-dotnet\"><strong>how I use NATS JetStream as a job queue in .NET</strong></a>.</p>\n<p>If you haven't tried it, spin up the container and publish a message.\nThat's all there is to getting started.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/getting-started-with-nats-jetstream-in-dotnet",
            "title": "Getting Started With NATS JetStream in .NET",
            "summary": "NATS almost never comes up when .NET developers talk about message queues, and that's a shame.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_200.png",
            "date_modified": "2026-06-27T00:00:00.000Z",
            "date_published": "2026-06-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-modular-monolith-boundary-i-couldnt-take-back",
            "content_html": "<p>I separated an order collaboration module from the product catalog, and a year of ordinary feature work wove the two back together.\nYou draw module boundaries when you know the least about the domain, so let consistency requirements decide where the line goes.\nSplitting a coarse module later is cheap, and merging two is the expensive direction.</p>\n<p>We built the system the way you're supposed to: a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>, one module per business domain, modules talking to each other through events so nothing was tightly coupled.</p>\n<p>The system let dealers and customers build an order together on a showroom floor.\nSo giving that order-building collaboration its own module, separate from the product catalog, was an easy decision to defend.\nThey were two distinct domains, and separating them into modules is textbook.</p>\n<p>It took about a year, and a piece of business context none of us had at the start, for that decision to quietly stop being reversible.</p>\n<h2>The Setup</h2>\n<p>This was the <a href=\"https://milanjovanovic.tech/blog/what-rewriting-a-40-year-old-project-taught-me-about-software-development\"><strong>40-year-old system I wrote about rewriting</strong></a>.\nThe backend is a manufacturing ERP, with the dealer ordering tool layered on top.</p>\n<p>Two areas felt obviously distinct.\nThe <strong>Catalog</strong> module owned products, configurations, options, and pricing.\nThe <strong>Collaboration</strong> module owned the back-and-forth of building an order: drafts, comments, approvals, revisions.\nIt looked like a clean separation: different responsibilities, different parts of the team.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_199/clean_boundary_day_one.png\" alt=\"Day one: Catalog and Collaboration as two cleanly separated modules, linked only by a single asynchronous events flow\">\n<p>Splitting them was the deliberate call, not a reflex, and every choice that followed held up on its own.</p>\n<h2>The Slow Snowball</h2>\n<p>The trouble started as ordinary feature work.</p>\n<p>A collaboration screen needed product options, so it read from the catalog.\nThen it needed live pricing too.\nThen a requirement landed: an order had to reflect catalog changes immediately.\nNone of them looked like an architecture decision.</p>\n<p>But every one of them added a thread between <code>Collaboration</code> and <code>Catalog</code>.\nThe two modules I had carefully separated were weaving themselves back together, one feature at a time.\nThe boundary was still there in the folder structure.\nIt had stopped being there in any way that mattered.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_199/coupling_a_year_later.png\" alt=\"Catalog and Collaboration a year later, linked by events, two reads, and a synchronous price check\">\n<h2>The Assumption That Aged Badly</h2>\n<p>The deeper problem was the communication style, and it's the decision I'd still defend hardest.\nAcross the whole rewrite, modules <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>communicated asynchronously</strong></a>, through events.\nIt kept modules decoupled during a high-stakes migration and let us replace the legacy system one piece at a time.\nCatalog publishes an event, Collaboration reacts when it gets to it, and the two stay independent.</p>\n<p>That held up right until the business needed an order to be correct <em>now</em>.\nA dealer changes a configuration, and the price has to be right the instant they hit save.\nEventual consistency had been the right assumption for every requirement we knew about.\nThe requirement that broke it didn't exist yet when we drew the boundary.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_199/eventual_vs_immediate_consistency.png\" alt=\"Eventual consistency on day one versus the later need for immediate consistency between Collaboration and Catalog\">\n<p>You can't bolt immediate consistency onto an asynchronous boundary.\nSo we did what everyone does: synchronous calls, shared transactions, and workarounds to paper over the gap.\nFix by fix, the system was telling us that, given the consistency the business now needed, these two belonged in one module.</p>\n<h2>The Signals That Didn't Look Like Signals</h2>\n<p>In hindsight, the signals were all there.\nAt the time, not one of them looked like a signal.</p>\n<ul>\n<li><strong>Collaboration constantly read Catalog's data.</strong>\nI took it for ordinary cross-module traffic, when it was really the boundary telling me the two belonged together.</li>\n<li><strong>Nearly every new Collaboration feature reached into Catalog.</strong>\nThat feels like healthy growth, right up until you notice it's merge pressure.</li>\n<li><strong>We kept adding event handlers just to keep the two in sync.</strong>\nIt passed for good event-driven design, and it was actually the coupling I wanted to avoid, wearing a disguise.</li>\n<li><strong>Then came the first &quot;this has to be correct immediately&quot; hotfix.</strong>\nEasy to wave off as a one-off, except it was the eventual-consistency assumption starting to crack.</li>\n</ul>\n<p>Any one of these is invisible.\nTogether, over a year, they're the whole story.</p>\n<h2>What I'd Tell My Past Self</h2>\n<p><strong>A module boundary is a guess, so treat it like one.</strong>\nYou're guessing - so keep testing the guess instead of filing it away as settled.</p>\n<p><strong>Start with fewer, coarser modules.</strong>\nYou can always split a module once the seam is obvious.\nWhen you're unsure, <a href=\"https://milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith\"><strong>keep things together</strong></a> and let a module earn its independence.\nIt's the same instinct as waiting for the <a href=\"https://milanjovanovic.tech/blog/dry-is-the-most-misunderstood-rule-in-programming\"><strong>third repetition before you extract an abstraction</strong></a>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_199/split_is_cheap_merge_is_expensive.png\" alt=\"Splitting one coarse module into two is cheap and low risk; merging two modules back into one is expensive and risky\">\n<p><strong>Let consistency draw your boundaries.</strong>\nEventual consistency across a boundary is a bet that they never will.\nMake that bet on purpose, not by default.</p>\n<p><strong>Watch the cheap decisions, not the expensive ones.</strong>\nWe pour deliberation into decisions that are easy to reverse and wave through the ones that quietly aren't.\nA module boundary feels cheap, because on the day you draw it, it is.\nThe cost shows up later, compounding, which is exactly why nobody's watching when it does.</p>\n<h2>The Part That's Uncomfortable</h2>\n<p>None of this means modular monoliths are a trap, or that async messaging is a mistake.\nI'd build it as a modular monolith again tomorrow.\nEvery call was sound for the context we had, and that context was incomplete in a way nobody could see yet.</p>\n<p>The lesson is smaller and harder to sit with: the boundaries you draw earliest are the ones most likely to be invalidated by what you learn later, and the least likely to be revisited once they are.\nThe door wasn't one-way when I walked through it.\nIt became one-way behind me, one reasonable feature at a time.</p>\n<h2>Summary</h2>\n<ul>\n<li>A module boundary is a <strong>guess</strong> made when you know the least, so treat it as provisional, not settled.</li>\n<li>Prefer <strong>fewer, coarser modules</strong>. Splitting one later is cheap - merging two back together is the expensive, risky direction.</li>\n<li>Let <strong>consistency</strong> draw your boundaries. If two things have to be correct at the same instant, they belong on the same side of the line.</li>\n</ul>\n<p>If you want a structured way to design module boundaries, and the judgment for when to keep modules together or split them apart, that's exactly what I teach in <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-modular-monolith-boundary-i-couldnt-take-back",
            "title": "The Modular Monolith Boundary I Couldn't Take Back",
            "summary": "We built the system exactly the way a modular monolith is supposed to be built, and that's how I ended up with two modules I couldn't pull apart a year later.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_199.png",
            "date_modified": "2026-06-20T00:00:00.000Z",
            "date_published": "2026-06-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-dapr-workflows-in-dotnet-with-aspire",
            "content_html": "<p>Dapr Workflow lets you write a long-running business process as ordinary C# code, and the sidecar makes it durable by replaying it from a state store after a crash.\n.NET Aspire runs the app, the sidecar, and the state store with a single <code>aspire run</code> command.\nAnything non-deterministic, like <code>DateTime.Now</code> or I/O, has to live in an activity.</p>\n<p>Most real business processes don't finish in a single request.</p>\n<p>An order gets placed, inventory gets checked, a payment gets charged, stock gets reserved, and the customer gets notified.\nEach step can fail, time out, or need a retry.\nAnd the whole thing has to survive a process restart without losing its place or charging someone twice.</p>\n<p>We usually solve this with a pile of queues, a state table, and a lot of defensive code to track where each process is.\nIt works, but the business logic ends up scattered across handlers and database rows, and nobody can read the flow top to bottom anymore.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/introduction-to-dapr-for-dotnet-developers\"><strong>Dapr</strong></a> (a graduated <a href=\"https://www.cncf.io/\">CNCF</a> project) has a building block for exactly this: <strong>Workflow</strong>.\nYou write the process as ordinary C# code, and Dapr makes it durable.\nIf the host crashes halfway through, the workflow picks up right where it left off.</p>\n<p>In this article, we'll build a small Dapr Workflow, run it with <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a>, and inspect its state with the <a href=\"https://docs.diagrid.io/develop/local-development/dev-dashboard/?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Diagrid Dev Dashboard</strong></a>.\nIf you'd rather learn this hands-on, there's also a free <a href=\"https://www.diagrid.io/dapr-university/dapr-workflows-dotnet-aspire?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Dapr University track</strong></a> built around this exact stack.</p>\n<p>Let's dive in.</p>\n<h2>What Dapr Workflow Actually Is</h2>\n<p>You define a <strong>workflow</strong> that orchestrates a process, and <strong>activities</strong> that do the actual work (call a database, hit an API, send an email).\nThis is <a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\"><strong>orchestration</strong></a> rather than choreography: one place drives the process instead of services reacting to each other's events.</p>\n<p>The definitions live in your app; the <strong>workflow engine</strong> that executes them runs in the Dapr sidecar:</p>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_198/workflow-overview.png\" alt=\"Diagram of a workflow app containing workflow and activity definitions, communicating over the Dapr API (HTTP/gRPC) with the Dapr Workflow engine running in the sidecar.\">\n  <figcaption>\n    Source: <a href=\"https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/\">Dapr - Workflows\noverview</a>\n  </figcaption>\n</figure>\n<p>The key idea is <strong>durable execution</strong>.\nDapr records every step to a state store, so the workflow can be replayed from history at any time.\nA crash, a deployment, or a scale-out event doesn't lose progress, and a workflow can run for seconds or for months.</p>\n<p>One rule follows from this: <strong>workflow code must be deterministic</strong>.\nNo <code>DateTime.Now</code>, no random values, no I/O - anything non-deterministic goes into an <em>activity</em>.\nEven logging is affected: use <code>context.CreateReplaySafeLogger&lt;T&gt;()</code> inside a workflow, or every replay will repeat your log lines.</p>\n<p>Under the hood, this all runs on Dapr actors, which is why the state store must support actors. More on that in a moment.</p>\n<h2>Building the Workflow</h2>\n<p>The quickest starting point is the <a href=\"https://aspire.dev/\"><strong>Aspire CLI's</strong></a> starter template:</p>\n<pre><code class=\"language-bash\">aspire new aspire-starter -n OrderProcessing\n</code></pre>\n<p>It generates the app host, an API service, and a <code>ServiceDefaults</code> project that wires up <a href=\"https://milanjovanovic.tech/blog/opentelemetry-dotnet-guide\"><strong>OpenTelemetry</strong></a> and health checks (that's where the <code>AddServiceDefaults</code> call comes from later).\nIf you're on Claude Code, the <a href=\"https://github.com/diagrid-labs/dapr-skills\"><strong>Dapr Skills</strong></a> plugin can scaffold the entire workflow project from a prompt and review it for determinism mistakes.\nEverything we build here is also in a <a href=\"https://github.com/m-jovanovic/dapr-workflows-with-aspire\"><strong>working sample on my GitHub</strong></a>, so you can clone it and follow along.</p>\n<p>The API service needs the <code>Dapr.Workflow</code> package:</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;Dapr.Workflow&quot; Version=&quot;1.18.1&quot; /&gt;\n</code></pre>\n<p>A workflow derives from <code>Workflow&lt;TInput, TOutput&gt;</code> and reads top to bottom like a normal method, even though every step is durably persisted:</p>\n<pre><code class=\"language-csharp\">using Dapr.Workflow;\n\nnamespace OrderApi.Workflows;\n\ninternal sealed class OrderProcessingWorkflow : Workflow&lt;OrderPayload, OrderResult&gt;\n{\n    public override async Task&lt;OrderResult&gt; RunAsync(\n        WorkflowContext context,\n        OrderPayload order)\n    {\n        // 1. Check inventory\n        var inventory = await context.CallActivityAsync&lt;InventoryResult&gt;(\n            nameof(CheckInventoryActivity),\n            order);\n\n        if (!inventory.InStock)\n        {\n            return new OrderResult(order.OrderId, &quot;Rejected: out of stock&quot;);\n        }\n\n        // 2. Charge the customer\n        await context.CallActivityAsync(\n            nameof(ProcessPaymentActivity),\n            new PaymentRequest(order.OrderId, order.TotalAmount));\n\n        // 3. Reserve the stock\n        await context.CallActivityAsync(\n            nameof(UpdateInventoryActivity),\n            order);\n\n        // 4. Notify the customer\n        await context.CallActivityAsync(\n            nameof(NotifyCustomerActivity),\n            order.CustomerId);\n\n        return new OrderResult(order.OrderId, &quot;Completed&quot;);\n    }\n}\n</code></pre>\n<p><code>CallActivityAsync</code> doesn't invoke the activity directly.\nIt schedules the work with the workflow engine, which records the result once the activity completes.\nIf the process dies after the payment step, Dapr replays the workflow, feeds it the recorded results for completed steps, and resumes at the inventory update.\nThe customer never gets charged twice.</p>\n<p>This is the <strong>task chaining</strong> pattern.\nDapr Workflow also supports fan-out/fan-in, external events, timers, and child workflows - all in plain C# (fan-out is just <code>Select</code> plus <code>Task.WhenAll</code>).\nIf you want to build the richer patterns hands-on, the free <a href=\"https://www.diagrid.io/dapr-university/dapr-workflows-dotnet-aspire?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Build Dapr Workflows in .NET with Aspire</strong></a> track has you fan out to parallel activities and aggregate the results.</p>\n<p>One production caveat: the recorded history is tied to the shape of your code, so changing a workflow while instances are in flight breaks their replay.\nThat's solved with <a href=\"https://www.diagrid.io/blog/how-to-version-net-workflows?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>workflow versioning</strong></a>; we're staying on version one here.</p>\n<p>An activity is where the real work happens, and the only place you're allowed to be non-deterministic.\nIt derives from <code>WorkflowActivity&lt;TInput, TOutput&gt;</code> and supports constructor injection:</p>\n<pre><code class=\"language-csharp\">using Dapr.Workflow;\n\nnamespace OrderApi.Activities;\n\ninternal sealed class CheckInventoryActivity(IInventoryService inventory)\n    : WorkflowActivity&lt;OrderPayload, InventoryResult&gt;\n{\n    public override async Task&lt;InventoryResult&gt; RunAsync(\n        WorkflowActivityContext context,\n        OrderPayload order)\n    {\n        bool inStock = await inventory.HasStockAsync(order.ProductId, order.Quantity);\n\n        return new InventoryResult(inStock);\n    }\n}\n</code></pre>\n<p>The other activities follow the same shape: charge the card, decrement stock, send the confirmation email.\nEach one is isolated, so Dapr can retry a failed activity without re-running the whole workflow.\nAnd since every input and output gets serialized to the state store, simple JSON-friendly records are the right tool for these types.</p>\n<h2>Starting Workflows Over HTTP</h2>\n<p>Register the workflow and its activities in the API service's <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddDaprWorkflow(options =&gt;\n{\n    options.RegisterWorkflow&lt;OrderProcessingWorkflow&gt;();\n\n    options.RegisterActivity&lt;CheckInventoryActivity&gt;();\n    options.RegisterActivity&lt;ProcessPaymentActivity&gt;();\n    options.RegisterActivity&lt;UpdateInventoryActivity&gt;();\n    options.RegisterActivity&lt;NotifyCustomerActivity&gt;();\n});\n</code></pre>\n<p>This also registers a <code>DaprWorkflowClient</code> we can use to start and query workflow instances:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/orders&quot;, async (\n    OrderPayload order,\n    DaprWorkflowClient workflowClient) =&gt;\n{\n    string instanceId = await workflowClient.ScheduleNewWorkflowAsync(\n        name: nameof(OrderProcessingWorkflow),\n        instanceId: order.OrderId,\n        input: order);\n\n    return Results.Accepted($&quot;/orders/{instanceId}&quot;, new { instanceId });\n});\n\napp.MapGet(&quot;/orders/{instanceId}&quot;, async (\n    string instanceId,\n    DaprWorkflowClient workflowClient) =&gt;\n{\n    WorkflowState? state = await workflowClient.GetWorkflowStateAsync(instanceId);\n\n    if (state is null || !state.Exists)\n    {\n        return Results.NotFound();\n    }\n\n    return Results.Ok(new\n    {\n        RuntimeStatus = state.RuntimeStatus.ToString(),\n        Output = state.ReadOutputAs&lt;OrderResult&gt;()\n    });\n});\n</code></pre>\n<p><code>ScheduleNewWorkflowAsync</code> returns immediately and the workflow runs in the background.\nIt's the same idea as <a href=\"https://milanjovanovic.tech/blog/how-to-scale-long-running-api-requests\"><strong>scaling long-running API requests</strong></a>: return <code>202 Accepted</code> and let the client poll for status.\nTwo SDK quirks worth knowing: <code>GetWorkflowStateAsync</code> returns <code>null</code> for an instance it has never seen, and <code>RuntimeStatus</code> is an enum that serializes as a bare number without the <code>ToString()</code>.</p>\n<h2>Running Everything With Aspire</h2>\n<p>Here's where Aspire earns its keep.\nA Dapr Workflow needs a sidecar and a state store running alongside the app, and Aspire orchestrates all of it from one place.</p>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_198/workflow-app-aspire.png\" alt=\"Architecture diagram: Aspire locally manages the workflow app and the Dapr sidecar with its workflow engine, which reads and writes workflow state to a state store. The Diagrid Dev Dashboard connects to the same state store to visualize workflow executions.\">\n  <figcaption>\n    Source: <a href=\"https://www.diagrid.io/dapr-university/dapr-workflows-dotnet-aspire?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\">Dapr\nUniversity</a>\n  </figcaption>\n</figure>\n<p>The app host needs two packages.\nThe Dapr integration lives in the <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/community-toolkit/overview\"><strong>Aspire Community Toolkit</strong></a> these days; the original <code>Aspire.Hosting.Dapr</code> package is deprecated.</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;CommunityToolkit.Aspire.Hosting.Dapr&quot; Version=&quot;13.0.0&quot; /&gt;\n&lt;PackageReference Include=&quot;Aspire.Hosting.Valkey&quot; Version=&quot;13.4.3&quot; /&gt;\n</code></pre>\n<p>Then the app host:</p>\n<pre><code class=\"language-csharp\">using CommunityToolkit.Aspire.Hosting.Dapr;\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nbuilder.AddDapr();\n\n// Pin the password. Aspire generates a random one on every run otherwise,\n// and the Dapr component file below has to know it.\nvar statePassword = builder.AddParameter(\n    &quot;statestore-password&quot;, &quot;state-store-123&quot;, secret: true);\n\n// Valkey (a Redis fork) as the workflow state store\nvar statestore = builder\n    .AddValkey(&quot;statestore&quot;, 16379, statePassword)\n    .WithDataVolume();\n\nbuilder.AddProject&lt;Projects.OrderApi&gt;(&quot;order-api&quot;)\n    .WithDaprSidecar(new DaprSidecarOptions\n    {\n        ResourcesPaths = [&quot;./Resources&quot;]\n    })\n    .WaitFor(statestore);\n\nbuilder.Build().Run();\n</code></pre>\n<p><code>WithDaprSidecar</code> runs a Dapr sidecar next to <code>order-api</code>, and <code>ResourcesPaths</code> points it at the Dapr component files (relative paths resolve against the app host directory).</p>\n<p>The one component the workflow needs is a state store - a <code>statestore.yaml</code> in the app host's <code>Resources</code> folder:</p>\n<pre><code class=\"language-yaml\">apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: workflowstore\nspec:\n  type: state.redis\n  version: v1\n  metadata:\n    - name: redisHost\n      value: 'localhost:16379'\n    - name: redisPassword\n      value: 'state-store-123'\n    - name: actorStateStore\n      value: 'true'\n</code></pre>\n<p>That <code>actorStateStore: &quot;true&quot;</code> line is the one people forget.\nDapr Workflow runs on top of actors, so without it, workflows won't run.\nNotice the application never sees any of this: swapping Valkey for Postgres means editing this YAML file, not your C# code.</p>\n<p>Install the <a href=\"https://docs.dapr.io/getting-started/install-dapr-cli/\"><strong>Dapr CLI</strong></a> and run <code>dapr init</code> once (that's where the sidecar binary comes from), then start everything with a single command:</p>\n<pre><code class=\"language-bash\">aspire run\n</code></pre>\n<p>Aspire spins up Valkey, the Dapr sidecar, and the API, with logs, traces, and health in one dashboard.\nGrab the API's port from there and post an order:</p>\n<pre><code class=\"language-bash\">curl -X POST http://localhost:5555/orders \\\n  -H &quot;Content-Type: application/json&quot; \\\n  -d '{&quot;orderId&quot;:&quot;order-001&quot;,&quot;customerId&quot;:&quot;cust-42&quot;,&quot;productId&quot;:&quot;pro-plan&quot;,&quot;quantity&quot;:2,&quot;totalAmount&quot;:49.99}'\n</code></pre>\n<p>Poll the status endpoint and you'll see the workflow march through its activities (if the first request returns a <code>500</code>, give the sidecar a few more seconds to connect to the placement service):</p>\n<pre><code class=\"language-bash\">curl http://localhost:5555/orders/order-001\n</code></pre>\n<pre><code class=\"language-json\">{\n  &quot;runtimeStatus&quot;: &quot;Completed&quot;,\n  &quot;output&quot;: {\n    &quot;orderId&quot;: &quot;order-001&quot;,\n    &quot;status&quot;: &quot;Completed&quot;\n  }\n}\n</code></pre>\n<p>Each activity shows up as a span in the distributed trace:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_198/aspire_distributed_trace_order_creation.png\" alt=\"Aspire dashboard distributed trace for POST /orders, showing the workflow orchestration span and individual spans for the CheckInventory, ProcessPayment, UpdateInventory, and NotifyCustomer activities.\">\n<h2>Inspecting Workflow State Locally</h2>\n<p>The Aspire dashboard shows you the request flow, but not the <em>workflow's</em> internal state: which step it's on, what each activity returned, and the full execution history.\nFor that, there's the <a href=\"https://docs.diagrid.io/develop/local-development/dev-dashboard/?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Diagrid Dev Dashboard</strong></a>: a free, local-only tool that reads your workflow state store (Redis-compatible, Postgres, or SQLite) and visualizes every instance.\nIt comes from Diagrid, the company founded by the creators of the Dapr OSS project, which also provides enterprise Dapr support.</p>\n<p>Since the whole point of this setup is that one command starts everything, let's add it to the app host:</p>\n<pre><code class=\"language-csharp\">builder.AddContainer(&quot;diagrid-dashboard&quot;, &quot;ghcr.io/diagridio/diagrid-dashboard:latest&quot;)\n    .WithBindMount(&quot;./Resources&quot;, &quot;/app/components&quot;)\n    .WithEnvironment(&quot;COMPONENT_FILE&quot;, &quot;/app/components/dashboard-store.yaml&quot;)\n    .WithEnvironment(&quot;APP_ID&quot;, &quot;diagrid-dashboard&quot;)\n    .WithHttpEndpoint(targetPort: 8080)\n    .WaitFor(statestore);\n</code></pre>\n<p>Why a second component file? Networking.\nThe sidecar runs as a host process, so <code>localhost:16379</code> works for it.\nThe dashboard runs in a container, where <code>localhost</code> means the container itself, so its <code>dashboard-store.yaml</code> reaches the host through <code>host.docker.internal</code>\n(on Linux without Docker Desktop, use the bridge gateway IP instead):</p>\n<pre><code class=\"language-yaml\">apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: dashboardstore\nspec:\n  type: state.redis\n  version: v1\n  metadata:\n    - name: redisHost\n      value: 'host.docker.internal:16379'\n    - name: redisPassword\n      value: 'state-store-123'\n    - name: actorStateStore\n      value: 'true'\nscopes:\n  - diagrid-dashboard\n</code></pre>\n<p>The <code>scopes</code> field keeps the API's sidecar from picking up this component, since both files sit in the same <code>Resources</code> folder.</p>\n<p>Run <code>aspire run</code> again and open the dashboard's endpoint from the Aspire resources view.\nEvery workflow instance is listed with its status, app ID, and duration:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_198/diagrid_dashboard_workflows.png\" alt=\"Diagrid Dev Dashboard listing OrderProcessingWorkflow executions with their status, instance ID, app ID, and start and end times.\">\n<p>Clicking an instance shows the exact input the workflow received and the output it produced:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_198/diagrid_dashboard_workflow_instance.png\" alt=\"Workflow Execution Details page showing a running OrderProcessingWorkflow instance with its status and input payload.\">\n<p>Below that sits the full execution history - the ground truth of what your workflow actually did.\nExpand a <code>TaskScheduled</code> event to see an activity's input, or a <code>TaskCompleted</code> event to see its input and output:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_198/diagrid_dashboard_workflow_history.png\" alt=\"Execution History table showing TaskScheduled and TaskCompleted events for each activity in the order processing workflow.\">\n<p>Being able to see the workflow's actual state, not just guess at it from logs, is what makes local workflow development feel sane.\nThere's also a <a href=\"https://www.nuget.org/packages/Diagrid.Aspire.Hosting.Dashboard\"><code>Diagrid.Aspire.Hosting.Dashboard</code></a> package that wraps this container setup into a single <code>AddDiagridDashboard</code> call.</p>\n<h2>Summary</h2>\n<p>Dapr Workflow gives you durable execution for long-running processes without dragging a heavy orchestration engine into your code:</p>\n<ul>\n<li>The process is <strong>plain C# code</strong> that reads top to bottom.</li>\n<li>Dapr makes it <strong>fault-tolerant</strong>, replaying from the state store so a crash never loses progress.</li>\n<li>The orchestration stays <strong>deterministic</strong>; the side effects live in activities.</li>\n<li><strong>Aspire</strong> runs the sidecar, the state store, and the dashboard with one command.</li>\n<li>The <strong>Diagrid Dev Dashboard</strong> shows you exactly what each instance is doing.</li>\n</ul>\n<p>You can grab the complete <a href=\"https://github.com/m-jovanovic/dapr-workflows-with-aspire\"><strong>source code for this article</strong></a> on my GitHub, including Aspire integration tests for the workflow.</p>\n<p>If you want to go deeper, the free <a href=\"https://www.diagrid.io/dapr-university/dapr-workflows-dotnet-aspire?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Build Dapr Workflows in .NET with Aspire</strong></a> track on Dapr University is the natural next step.\nYou'll build a fan-out/fan-in workflow on this exact stack in a hosted sandbox, with nothing to install.\nThe <a href=\"https://www.diagrid.io/dapr-university/dapr-workflow?utm_source=milanjovanovic&utm_medium=referral&utm_campaign=workflows\"><strong>Dapr Workflow track</strong></a> covers the remaining patterns in standalone examples.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-dapr-workflows-in-dotnet-with-aspire",
            "title": "Building Dapr Workflows in .NET With Aspire",
            "summary": "Long-running business processes are hard to get right. Dapr Workflow lets you write them as plain C# code that survives crashes and restarts, and Aspire makes…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_198.png",
            "date_modified": "2026-06-13T00:00:00.000Z",
            "date_published": "2026-06-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/dry-is-the-most-misunderstood-rule-in-programming",
            "content_html": "<p>The original DRY rule is about knowledge, not code that looks the same.\nA fact like a tax rule should have one authoritative home, while two blocks that only resemble each other are separate concepts that will drift.\nExtract on the third occurrence, and only when both copies must change together.</p>\n<p>Every developer learns DRY early, and almost everyone learns it wrong.</p>\n<p>Don't Repeat Yourself.\nSee two pieces of code that look the same, extract a method, delete the duplicate.\nI did this for years and wrote some of the worst code I've ever had to maintain:</p>\n<ul>\n<li>A shared helper that grew a new boolean parameter every sprint.</li>\n<li>A base class nobody dared touch because six unrelated features inherited from it.</li>\n<li>A &quot;common&quot; module two independent parts of the system both depended on, so neither could change without the other.</li>\n</ul>\n<p>Every one started as an innocent attempt to not repeat myself.</p>\n<h2>What DRY Actually Says</h2>\n<p>Here's the part most people skip.\nThe original definition, from Andy Hunt and Dave Thomas in <a href=\"https://en.wikipedia.org/wiki/The_Pragmatic_Programmer\"><em>The Pragmatic Programmer</em></a>,\nsays nothing about code:</p>\n<blockquote>\n<p>Every piece of <strong>knowledge</strong> must have a single, unambiguous, authoritative representation within a system.</p>\n</blockquote>\n<p>It's about knowledge. A single <em>fact</em> about your domain, like a tax rule or the format of an invoice number,\nshould live in exactly one place.\nWhen that fact changes, you change it once instead of hunting for seven copies.</p>\n<h2>The Mistake: Deduplicating Code, Not Knowledge</h2>\n<p>Two pieces of code can look identical and represent completely different knowledge.</p>\n<p>Say you validate two addresses.\nOne is a customer's shipping address, the other is a warehouse address.\nToday the rules are identical:</p>\n<pre><code class=\"language-csharp\">public bool IsValid(Address address) =&gt;\n    !string.IsNullOrWhiteSpace(address.Street) &amp;&amp;\n    !string.IsNullOrWhiteSpace(address.City) &amp;&amp;\n    !string.IsNullOrWhiteSpace(address.PostalCode);\n</code></pre>\n<p>The DRY reflex says extract one validator and call it from both places.\nBut these are different concepts that happen to share rules this week.\nThe day the warehouse needs a loading-dock code,\nyou're back in the shared method bolting on a flag to keep the other caller working:</p>\n<pre><code class=\"language-csharp\">public bool IsValid(Address address, bool requireDockCode = false) =&gt;\n    !string.IsNullOrWhiteSpace(address.Street) &amp;&amp;\n    !string.IsNullOrWhiteSpace(address.City) &amp;&amp;\n    !string.IsNullOrWhiteSpace(address.PostalCode) &amp;&amp;\n    (!requireDockCode || !string.IsNullOrWhiteSpace(address.DockCode));\n</code></pre>\n<p>That boolean is the tell.\nThe first time a shared method grows a flag so one caller behaves differently, you didn't have duplication.\nYou had two things that looked alike and glued them together.\nGive it a year and the signature has three more flags,\neach one a place where the two concepts were never actually the same.</p>\n<h2>The Wrong Abstraction Costs More Than Duplication</h2>\n<p><strong>Duplication is far cheaper than the wrong abstraction</strong>.</p>\n<p>Copy-paste is visible and local.\nYou can see both copies, and if they drift apart, that was always allowed.\nThe wrong abstraction is invisible and global.\nEvery caller depends on one shape and bends it to fit, the flags pile up,\nand you end up afraid to touch a method you no longer understand.\nI've spent more time deleting bad abstractions than I ever saved writing them.</p>\n<p>This is the <a href=\"https://milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet\"><strong>hidden coupling cost I wrote about in the abstractions piece</strong></a>,\nand DRY-by-reflex is one of the most common ways it sneaks in.</p>\n<h2>Where It Hurts Most: Across Boundaries</h2>\n<p>Inside one class, a bad helper is annoying. Across module boundaries, it's structural damage.</p>\n<p>Picture a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a> with a <code>Billing</code> module and a <code>Shipping</code> module.\nBoth have an <code>Order</code>.\nA well-meaning engineer notices the two classes share fields and pulls them into a shared type both modules reference:</p>\n<pre><code class=\"language-csharp\">// Shared.Orders, referenced by both Billing and Shipping\npublic class Order\n{\n    public Guid Id { get; set; }\n    public string CustomerName { get; set; }\n    public decimal Total { get; set; }\n    // ...whatever either module happens to need\n}\n</code></pre>\n<p>Now Billing and Shipping can't evolve independently.\nA change to billing's order forces a recompile, re-test, and redeploy of shipping.\nYou took two <a href=\"https://milanjovanovic.tech/blog/bounded-context-ddd-explained\"><strong>bounded contexts</strong></a> that were supposed to be decoupled and welded them together to save a few properties.</p>\n<p>Two modules each owning their own <code>Order</code> is the whole point of <a href=\"https://milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith\"><strong>keeping data inside its boundaries</strong></a>. The shapes are allowed to be similar, modeling the same real-world thing from two points of view that drift over time. It's the same reason <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-is-easier-than-you-think\"><strong>vertical slices</strong></a>\ntolerate a little repetition, so each slice can change on its own.</p>\n<h2>The Rule I Use: Wait for the Third Time</h2>\n<p>I don't deduplicate the second time I see something.\nI wait for the third, and I ask one question: if this rule changes, do both copies have to change together?</p>\n<ul>\n<li><strong>Yes</strong> - it's real duplication, the same fact written in several places. Extract it, and that's DRY doing its job.</li>\n<li><strong>No</strong> - the resemblance is a coincidence. Leave it alone, and coupling them will cost you later.</li>\n</ul>\n<p>Let the code repeat until the right abstraction becomes obvious,\nbecause good abstractions are discovered from concrete cases, not guessed up front.\nSome people call this AHA, for &quot;Avoid Hasty Abstractions.&quot;</p>\n<p>A practical tell: extract when you can name the concept.\nA real domain name like <code>Money</code>, <code>TaxRate</code>, or <code>InvoiceNumber</code> is probably knowledge worth a <a href=\"https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals\"><strong>value object</strong></a>.\nIf the best name you can find is <code>Helper</code>, <code>Utils</code>, or <code>ProcessData</code>, you're abstracting shape, not knowledge.</p>\n<h2>When DRY Is Right</h2>\n<p>Applied correctly, DRY is invaluable.\nA business rule belongs in exactly one place.\nWatch what happens when &quot;an order over $1,000 needs manager approval&quot; gets copy-pasted across three services:</p>\n<pre><code class=\"language-csharp\">// OrderService\nif (order.Total &gt; 1000) { /* require approval */ }\n\n// CheckoutService\nif (order.Total &gt; 1000m) { /* require approval */ }\n\n// AdminController - someone bumped the limit here, and only here\nif (order.Total &gt; 5000) { /* require approval */ }\n</code></pre>\n<p>You will eventually update two of them and ship a bug.\nThat drifted third copy is exactly how it happens.\nPush the rule into the <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>domain model</strong></a> where it has one home:</p>\n<pre><code class=\"language-csharp\">public bool RequiresManagerApproval() =&gt; Total &gt; 1000;\n</code></pre>\n<p>That's the single authoritative representation DRY is actually about.</p>\n<h2>Summary</h2>\n<ul>\n<li>DRY is about <strong>knowledge</strong>, not code that looks alike.</li>\n<li>The wrong abstraction costs more than the duplication it replaced, and it's harder to undo.</li>\n<li>Wait for the third occurrence. Extract only when both copies encode the same fact and must change together.</li>\n</ul>\n<p>The next time you're about to delete a duplicate, don't ask whether the code looks the same.\nAsk whether it <em>means</em> the same.\nThat one question will save you more maintenance pain than DRY ever saved you typing.</p>\n<p>If you want to see how I draw these boundaries in a real system,\nwith independent modules and abstractions that earn their place, that's the heart of <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/dry-is-the-most-misunderstood-rule-in-programming",
            "title": "DRY Is the Most Misunderstood Rule in Programming",
            "summary": "DRY was never about code that looks the same. It's about knowledge. Most of the tangled abstractions I've had to unwind started as a well-meaning attempt to…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_197.png",
            "date_modified": "2026-06-06T00:00:00.000Z",
            "date_published": "2026-06-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/union-types-are-finally-coming-to-csharp",
            "content_html": "<p>C# 15, shipping with .NET 11, adds union types as a preview feature.\nA union declares a closed set of types, so a method can return a <code>User</code> or a <code>NotFound</code> and nothing else.\nPattern matching over it is exhaustive, so the compiler flags every <code>switch</code> that misses a case.</p>\n<p>Every backend developer eventually hits the same wall: a method that can return <em>one of several things</em>.</p>\n<p>A parse that either gives you a number or an error.\nA lookup that returns a value or &quot;not found&quot;.\nAn operation that succeeds or fails.\nIn C#, we've never had a clean way to model &quot;this is an <code>A</code> <strong>or</strong> a <code>B</code>&quot;.\nSo we faked it - with marker interfaces, abstract base classes, tuples, nullable returns, exceptions,\nor the excellent <a href=\"https://github.com/mcintyre321/OneOf\"><strong>OneOf</strong></a> library.</p>\n<p>C# 15 (shipping with .NET 11) finally adds <strong>union types</strong> to the language.\nI've wanted this for years, so let me give you a quick tour.</p>\n<p>Let's dive in.</p>\n<h2>The Problem</h2>\n<p>Say a method can return a user or fail because they don't exist. Today you'd reach for something like this:</p>\n<pre><code class=\"language-csharp\">// Throw for the &quot;failure&quot; case - control flow via exceptions\npublic User GetUser(int id) =&gt;\n    _users.TryGetValue(id, out var user)\n        ? user\n        : throw new UserNotFoundException(id);\n</code></pre>\n<p>The signature says it returns a <code>User</code>, but that's a lie - it might throw instead. The caller has no way to know that without reading the body. The other usual workarounds (a bool <code>TryGet</code> with an <code>out</code> parameter, a <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>custom <code>Result</code> class</strong></a> with nullable fields, or a <code>OneOf&lt;User, NotFound&gt;</code>) all add ceremony to express one simple idea.</p>\n<p>What you actually want is a <strong>closed set</strong> of types. That's exactly what a union is.</p>\n<h2>Declaring a Union</h2>\n<p>The syntax is delightfully small. You list a name and the case types:</p>\n<pre><code class=\"language-csharp\">public union Result&lt;T&gt;(T, Exception);\n</code></pre>\n<p>That's it. A <code>Result&lt;T&gt;</code> is now <em>either</em> a <code>T</code> <em>or</em> an <code>Exception</code> - and nothing else. The types don't even need to be related, which is the whole point.</p>\n<p>Here's a more concrete example with unrelated <a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>record types</strong></a>:</p>\n<pre><code class=\"language-csharp\">public record CreditCard(string Last4, string Brand);\npublic record PayPal(string Email);\npublic record BankTransfer(string Iban);\n\npublic union PaymentMethod(CreditCard, PayPal, BankTransfer);\n</code></pre>\n<h2>Creating Values</h2>\n<p>There's an implicit conversion from each case type, so you just assign the value directly:</p>\n<pre><code class=\"language-csharp\">PaymentMethod method = new CreditCard(&quot;4242&quot;, &quot;Visa&quot;);\n</code></pre>\n<p>Try to assign a type that isn't in the set, and it's a <strong>compile error</strong>. The set is closed.</p>\n<h2>Consuming a Union</h2>\n<p>This is where it shines. Pattern matching just works, and the compiler checks the inner value for you:</p>\n<pre><code class=\"language-csharp\">string Describe(PaymentMethod method) =&gt; method switch\n{\n    CreditCard card  =&gt; $&quot;{card.Brand} ending {card.Last4}&quot;,\n    PayPal paypal    =&gt; $&quot;PayPal ({paypal.Email})&quot;,\n    BankTransfer ach =&gt; $&quot;Bank transfer to {ach.Iban}&quot;,\n}; // No `_` or `default` needed\n</code></pre>\n<p>Notice there's <strong>no discard <code>_</code> and no <code>default</code> arm</strong>. Because the union is closed, the compiler knows all three cases are covered. Forget one, and you get a warning at compile time:</p>\n<pre><code>warning CS8509: The switch expression does not handle all possible values\nof its input type (it is not exhaustive). For example, the pattern 'BankTransfer'\nis not covered.\n</code></pre>\n<p>That exhaustiveness check is the feature I'm most excited about. Add a new case to the union later, and the compiler points you at every <code>switch</code> you forgot to update.</p>\n<h2>Back to The Problem</h2>\n<p>Remember our lying <code>GetUser</code> method from earlier? Let's fix it with a union.</p>\n<p>First, declare what the method can actually return - a <code>User</code> or a <code>NotFound</code>:</p>\n<pre><code class=\"language-csharp\">public record NotFound(int Id);\n\npublic union UserResult(User, NotFound);\n</code></pre>\n<p>Now the signature tells the truth, and there are no exceptions for control flow:</p>\n<pre><code class=\"language-csharp\">public UserResult GetUser(int id) =&gt;\n    _users.TryGetValue(id, out var user)\n        ? user\n        : new NotFound(id);\n</code></pre>\n<p>And the caller has to handle both outcomes - the compiler won't let them forget:</p>\n<pre><code class=\"language-csharp\">IResult response = GetUser(42) switch\n{\n    User user      =&gt; Results.Ok(user),\n    NotFound found =&gt; Results.NotFound($&quot;No user with id {found.Id}&quot;),\n};\n</code></pre>\n<p>That's the whole pitch. The return type <em>tells you the truth</em>: here are exactly the shapes you'll get back, and you can't ignore one by accident. No more reading the method body to discover what it might throw.</p>\n<h2>A Few Caveats</h2>\n<p>This is still a <strong>preview/experimental</strong> feature. A few things to keep in mind:</p>\n<ul>\n<li>It targets <strong>C# 15 / .NET 11</strong>, and the syntax may still change before release. Try it on .NET 11 Preview 4 or later.</li>\n<li>Under the hood, a union is compiled to a <code>struct</code> that boxes value-type cases and stores the contents as a single <code>object?</code>. There's a non-boxing path for performance-sensitive code, but the default is simple.</li>\n<li>This is a <em>type</em> union (an <code>A</code> or a <code>B</code>), not a full discriminated union with named cases yet. It covers the vast majority of what I reach for OneOf for today.</li>\n</ul>\n<h2>Summary</h2>\n<p>Union types close a gap that's been open in C# for a very long time.</p>\n<ul>\n<li>Declare a <strong>closed set</strong> of types with <code>public union Name(A, B, C);</code>.</li>\n<li>Assign case values <strong>directly</strong> - implicit conversions handle the rest.</li>\n<li><strong>Pattern match</strong> with full compiler-checked exhaustiveness, no <code>default</code> arm required.</li>\n<li>Model results, options, and &quot;one of these&quot; returns <strong>without</strong> marker interfaces, base classes, or extra libraries.</li>\n</ul>\n<p>It's a small syntax with a big payoff: your method signatures finally tell the truth about what they return, and the compiler keeps every <code>switch</code> honest.</p>\n<p>I'll explore this feature more in the future, but I wanted to share this quick tour now that it's available in preview.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/union-types-are-finally-coming-to-csharp",
            "title": "Union Types Are Finally Coming to C#",
            "summary": "For years we faked union types with marker interfaces, base classes, and the OneOf library.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_196.png",
            "date_modified": "2026-05-30T00:00:00.000Z",
            "date_published": "2026-05-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-scale-long-running-api-requests",
            "content_html": "<p>A long-running endpoint should accept the work instead of doing it.\nValidate the input, persist a job row with a <code>Pending</code> status, and return <code>202 Accepted</code> with an ID the client can poll.\nWhen the in-process worker starts competing with your API for CPU and connections, move it behind a queue so the two sides scale independently.</p>\n<p>Every system I've worked on eventually grows an endpoint that takes minutes to finish (or longer).\nA report that aggregates years of data. A bulk import. A workflow that fans out to three external services and a database before it can answer.</p>\n<p>You end up with two problems at once.\nYour users sit on a spinner for several minutes, and your API holds that request open the entire time - burning a thread, a connection, and a slot in your concurrency budget.\nA small traffic spike on that one endpoint could potentially take the rest of the API down with it.</p>\n<p>I want to walk through the progression I use to fix this.\nIt's the same path the diagram below traces, from &quot;the request just blocks&quot; to a fully decoupled,\nqueue-backed worker pool - the shape I usually call an <a href=\"https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way\"><strong>async API</strong></a>.</p>\n<p>Depending on your requirements, you might stop at any point along the way - but I want to make sure you understand the full path and the trade-offs at each step.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_195/scaling_long_running_requests.png\" alt=\"System design progression for scaling long-running API requests, from synchronous request to queue-based competing consumers.\">\n<h2>Step 0: The Naive Version</h2>\n<p>A user sends a request. The application server does the work. The work takes five minutes. The connection stays open the whole time.</p>\n<p>There is nothing <em>wrong</em> with this approach - it's just paying for correctness with availability.\nThe user experience is bad, and the blast radius is large: every long request you accept is a request you <em>can't</em> accept somewhere else.</p>\n<p>The first realization you need to internalize is that <strong>the response time and the work duration don't have to be the same thing</strong>.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_195/blocking_api_request.png\" alt=\"Diagram of a blocking API request, where the user sends a request and waits for the work to finish before getting a response.\">\n</div>\n<h2>Step 1: Accept the Work, Don't Do It</h2>\n<p>The first move is to stop doing the work inside the request.</p>\n<p>I add a <code>jobs</code> table that represents the work I <em>intend</em> to do. The API endpoint now does three things:</p>\n<ol>\n<li>Validate the request.</li>\n<li>Insert a row into <code>jobs</code> with status <code>Pending</code>.</li>\n<li>Return <code>202 Accepted</code> with a job ID.</li>\n</ol>\n<p>A <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>background processor</strong></a> running inside the same API picks up <code>Pending</code> rows and works through them.\nThe client either polls a <code>GET /jobs/{id}</code> endpoint or - better - I push updates via <a href=\"https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr\"><strong>SignalR</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/server-sent-events-in-aspnetcore-and-dotnet-10\"><strong>Server-Sent Events</strong></a>, or email when the job is done.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_195/202_accepted_background_processor.png\" alt=\"Diagram of an API request that accepts work and returns 202, with a background processor that picks up pending jobs and processes them asynchronously.\">\n<p>This already buys you a lot.\nThe endpoint returns in milliseconds, the user gets a job ID they can track, and a spike of incoming requests just becomes a spike of rows in a table.\nThat table is cheap to write to.</p>\n<p>But there's a ceiling here, and it's easy to hit.</p>\n<h2>Step 2: Decouple the Worker From the API</h2>\n<p>The background processor in Step 1 still lives inside your API process.\nIt competes for the same CPU, memory, and connection pool as your real endpoints.\nIf processing gets heavy or slow, your API starts feeling it - the very thing you were trying to avoid.</p>\n<p>The fix is to pull the background processor out into its own deployable, and put a queue between the two.</p>\n<p>The API now publishes a message to the queue (and optionally writes the job row for tracking).\nA pool of background workers consumes from the queue and does the actual work - the same shape I covered in <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>event-driven architecture with RabbitMQ</strong></a>.\nThis is the <strong>competing consumers</strong> pattern, and it gives you something the previous step couldn't: <strong>independent scaling</strong>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_195/scaling_long_running_requests_final.png\" alt=\"System design progression for scaling long-running API requests, from synchronous request to queue-based competing consumers.\">\n<p>Three things change once you cross this line:</p>\n<ul>\n<li><strong>The queue absorbs spikes.</strong> Your API can keep accepting work at a constant rate while the workers drain at their own pace.</li>\n<li><strong>You scale workers separately from the API.</strong> More throughput on background jobs doesn't mean more API instances.</li>\n<li><strong>Failures become normal.</strong> A worker crash is just a message that goes back on the queue, not a 500 to your user.</li>\n</ul>\n<p>You also get retryability, pause/resume, structured error handling, and a <strong>dead-letter queue</strong> for poison messages - effectively for free, because the queue infrastructure already provides them.</p>\n<h2>What This Costs You</h2>\n<p>I'd be lying if I said this was a free upgrade.</p>\n<p>You're now running a queue, a worker fleet, and a notification path. That's more moving parts to deploy, monitor, and alert on.\nYour &quot;is this done yet?&quot; semantics are no longer obvious from the HTTP response - the client has to ask, or you have to tell them.\nAnd every job needs to be <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>idempotent</strong></a>, because at-least-once delivery means your workers <em>will</em> see duplicates.</p>\n<p>If you only have one slow endpoint and modest traffic, this is overkill.\nA simple &quot;fire-and-forget with status polling&quot; inside the same process is fine.\nDon't reach for a queue until the pain justifies it.</p>\n<h2>When I'd Use a Cloud Service Instead</h2>\n<p>You don't always need to assemble this from parts. A few alternatives I would consider:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit\"><strong>AWS SQS</strong></a> <strong>+ Lambda</strong> or <a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\"><strong>Azure Service Bus</strong></a> <strong>+ Azure Functions</strong> when I want the worker pool to scale to zero and I don't want to manage hosts.</li>\n<li><strong>Azure Durable Functions</strong> or <strong>AWS Step Functions</strong> when the work is a multi-step workflow with timers, retries, and human approvals. Orchestration is what they're good at.</li>\n<li><a href=\"https://temporal.io/\"><strong>Temporal</strong></a> when the workflow is long-lived (hours, days) and I need first-class durable execution, versioning, and visibility across runs.</li>\n</ul>\n<p>The trade-off is the usual one: less operational work, more vendor coupling, and a pricing model you need to model carefully when throughput grows.</p>\n<h2>Summary</h2>\n<p>The progression is simple, and it generalizes:</p>\n<ol>\n<li><strong>Don't do slow work inside the request.</strong> Accept it, persist it, return <code>202</code>.</li>\n<li><strong>Don't run workers inside the API.</strong> Put a queue in between and scale the two sides independently.</li>\n<li><strong>Tell the user when it's done.</strong> Polling is fine, push is better.</li>\n</ol>\n<p>This isn't a microservices argument. It's a separation between <em>accepting work</em> and <em>doing work</em> - two concerns that have very different scaling profiles and very different failure modes.</p>\n<p>If you want the full implementation walkthrough, the <a href=\"https://youtu.be/U40HzU_KkDY\"><strong>video version is here</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-scale-long-running-api-requests",
            "title": "How to Scale Long-Running API Requests",
            "summary": "When a single API call takes minutes to finish, it punishes both your users and your server.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_195.png",
            "date_modified": "2026-05-23T00:00:00.000Z",
            "date_published": "2026-05-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/when-your-use-case-half-succeeds-designing-for-partial-failure-in-dotnet",
            "content_html": "<p>A use case is not a transaction.\nOnce it touches more than one system, some side effects can succeed while others fail, and you have to design for that.\nClassify every side effect as transactional, external and reversible, or external and irreversible, then commit the transactional work last and push irreversible work through the outbox.</p>\n<p>One of the recurring bugs I've chased over the years is the &quot;duplicate charge&quot; support ticket.</p>\n<p>The customer was charged once, but our system thought the payment had failed.\nThe payment provider had taken the money, the order had rolled back to a draft state, and the user had received a &quot;Payment failed, please retry&quot; email on top of it.\nA single user action left every subsystem with a different opinion about what actually happened.</p>\n<p>A use case looks like a transaction because it sits behind a single method call.\nBut the moment it touches more than one system, you are dealing with <strong>partial failure</strong>.</p>\n<p>Here's how I think about it:</p>\n<ul>\n<li>The three categories every side effect falls into</li>\n<li>How to design use cases that fail loudly and recover safely</li>\n<li>When to reach for the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a> and when not to</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Code That Looks Fine</h2>\n<p>Here's a typical &quot;place an order&quot; use case.\nI've written this exact shape a hundred times, and so have you.</p>\n<pre><code class=\"language-csharp\">internal sealed class PlaceOrder(\n    IOrderRepository orders,\n    IPaymentService payments,\n    IEmailService emails,\n    IUnitOfWork unitOfWork)\n{\n    public async Task&lt;Result&gt; ExecuteAsync(PlaceOrderRequest request, CancellationToken ct)\n    {\n        var order = Order.Create(request.CustomerId, request.Items);\n        orders.Insert(order);\n\n        await payments.ChargeAsync(order.Id, order.Total, ct);\n\n        await emails.SendOrderConfirmationAsync(order.Id, ct);\n\n        await unitOfWork.SaveChangesAsync(ct);\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>There are three side effects in this method, sitting behind what looks like a single transactional boundary, with no coordination between them.</p>\n<p>If <code>SaveChangesAsync</code> throws <em>after</em> <code>ChargeAsync</code> succeeded, you've taken the customer's money and lost the order.\nIf <code>SendOrderConfirmationAsync</code> throws, the order saves and the charge goes through, but no email is sent.\nAnd if you naively retry, you double-charge.</p>\n<p>The use case &quot;works&quot; until it doesn't, and when it doesn't, it tends to fail in a different way every time.</p>\n<h2>Three Categories of Side Effect</h2>\n<p>Before you write a single line of recovery code, classify every side effect into one of three buckets:</p>\n<ol>\n<li><strong>Transactional</strong> - lives inside your database transaction. Inserts, updates, <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>domain events</strong></a> dispatched in-process.</li>\n<li><strong>External and reversible</strong> - an API call you can compensate for. Charge → refund. Reserve inventory → release.</li>\n<li><strong>External and irreversible</strong> - sent emails, posted webhooks, SMS messages. Once they're out, they're out.</li>\n</ol>\n<p>The category determines the strategy. There is no single &quot;handle errors properly&quot; rule that covers all three.</p>\n<h2>Strategy 1: Pull Transactional Work to the End</h2>\n<p>The first move is mechanical.\nAnything transactional should commit <em>last</em>, after all external calls have either succeeded or been explicitly tolerated.</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; ExecuteAsync(PlaceOrderRequest request, CancellationToken ct)\n{\n    var order = Order.Create(request.CustomerId, request.Items);\n\n    var charge = await payments.ChargeAsync(order.Id, order.Total, ct);\n    if (charge.IsFailure) return charge;\n    order.MarkPaid(charge.Value.TransactionId);\n\n    orders.Insert(order);\n\n    await unitOfWork.SaveChangesAsync(ct);\n    return Result.Success();\n}\n</code></pre>\n<p>You can't always do this - sometimes you need a database ID before calling the external service.\nThat's fine.\nThe point isn't ordering for its own sake. It's making sure that if you commit, you've already done the work the commit promises.</p>\n<h2>Strategy 2: Move Irreversible Side Effects Outside the Use Case</h2>\n<p>This is where the <a href=\"https://milanjovanovic.tech/blog/scaling-the-outbox-pattern\"><strong>Outbox pattern</strong></a> earns its keep.</p>\n<p>Instead of sending the email directly, raise an <code>OrderPlaced</code> <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>domain event</strong></a> and let an outbox dispatcher pick it up <em>after</em> the transaction commits.</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; ExecuteAsync(PlaceOrderRequest request, CancellationToken ct)\n{\n    var order = Order.Create(request.CustomerId, request.Items);\n\n    var charge = await payments.ChargeAsync(order.Id, order.Total, ct);\n    if (charge.IsFailure) return charge;\n    order.MarkPaid(charge.Value.TransactionId);\n\n    orders.Insert(order);\n    order.Raise(new OrderPlacedEvent(order.Id));\n    await unitOfWork.SaveChangesAsync(ct);\n\n    return Result.Success();\n}\n</code></pre>\n<p>The email is no longer the use case's problem.\nIf the transaction commits, the event commits with it inside the same write.\nIf it doesn't, the event never escapes the database and no email is ever sent.\nA separate worker turns events into emails, with its own retries and its own <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>idempotency</strong></a> guarantees.</p>\n<h2>Strategy 3: Make External Calls Idempotent or Compensable</h2>\n<p>The payment call is the dangerous one.\nIf it succeeds and your transaction rolls back, you've taken money you can't account for.</p>\n<p>What you do <em>not</em> do is silently swallow the failure:</p>\n<pre><code class=\"language-csharp\">try\n{\n    await payments.ChargeAsync(order.Id, order.Total, ct);\n}\ncatch\n{\n    // shrug\n}\n</code></pre>\n<p>The symptom disappears from your logs, the money stays gone, and the next time the user retries you charge them again.</p>\n<p>There are two approaches I actually use, and they compose well together.</p>\n<h3>Approach A: Idempotency Keys</h3>\n<p>Most serious payment providers (Stripe, Adyen, Braintree) let you attach an <strong>idempotency key</strong> to a charge.\nA retry with the same key is a no-op on their side and returns the original result.\nThe natural key here is the order ID:</p>\n<pre><code class=\"language-csharp\">var charge = await payments.ChargeAsync(\n    new ChargeRequest\n    {\n        OrderId = order.Id,\n        Amount = order.Total,\n        IdempotencyKey = order.Id.ToString()\n    },\n    ct);\n</code></pre>\n<p>Now it's safe to retry the use case.\nIf the previous attempt charged the customer and crashed before committing, the next attempt gets the <em>same</em> charge back from the provider instead of creating a new one, and the order finally gets persisted.</p>\n<h3>Approach B: Compensate via a Domain Event</h3>\n<p>Idempotency keys only help when you can replay with the same inputs.\nSometimes you can't - the user gave up, the request was cancelled, or the failure is permanent.</p>\n<p>In that case, the money is real and needs to come back.\nMake the failure itself a first-class event and refund out-of-band:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; ExecuteAsync(PlaceOrderRequest request, CancellationToken ct)\n{\n    var order = Order.Create(request.CustomerId, request.Items);\n\n    var charge = await payments.ChargeAsync(order.Id, order.Total, ct);\n    if (charge.IsFailure) return charge;\n    order.MarkPaid(charge.Value.TransactionId);\n\n    try\n    {\n        orders.Insert(order);\n        order.Raise(new OrderPlacedEvent(order.Id));\n        await unitOfWork.SaveChangesAsync(ct);\n    }\n    catch (Exception ex)\n    {\n        await outbox.PublishAsync(\n            new PaymentFailedEvent(\n                order.Id,\n                charge.Value.TransactionId,\n                order.Total,\n                Reason: ex.Message),\n            ct);\n        throw;\n    }\n\n    return Result.Success();\n}\n</code></pre>\n<p>A background consumer subscribes to <code>PaymentFailedEvent</code> and issues the refund, using the transaction ID as its own idempotency key.\nThis turns a scary cross-process compensation into a normal, observable, retryable <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>message handler</strong></a>.</p>\n<p>In practice, I use Approach A for transient failures and Approach B for permanent ones. They aren't mutually exclusive.</p>\n<h2>When the Saga Pattern Wins Instead</h2>\n<p>The strategies above work when one use case coordinates a small number of side effects in a single service.\nOnce the work spans multiple services and needs to survive process restarts, you're in <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>saga</strong></a> territory.</p>\n<p>The rule I use: if you can fit the recovery logic in your head, a well-designed use case is enough. If you can't, reach for a saga.</p>\n<h2>Summary</h2>\n<p>A use case is a unit of <em>intent</em>, not a unit of <em>atomicity</em>.</p>\n<ul>\n<li><strong>Transactional</strong> work commits with the database, last.</li>\n<li><strong>Irreversible</strong> work goes through the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a>, not the use case.</li>\n<li><strong>External, reversible</strong> work uses idempotency keys first, compensating events second.</li>\n<li><strong>Never</strong> swallow failures to make the use case &quot;look successful&quot;.</li>\n</ul>\n<p>Most of the production bugs I've debugged in event-driven systems come down to a use case that lied about whether it succeeded.\nStop lying, and the system gets a lot easier to reason about.</p>\n<p>If you want to see this kind of thinking applied across a full system, that's exactly what I build inside <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/when-your-use-case-half-succeeds-designing-for-partial-failure-in-dotnet",
            "title": "When Your Use Case Half-Succeeds: Designing for Partial Failure in .NET",
            "summary": "A use case isn't a transaction. The moment it touches more than one system, you are dealing with partial failure.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_194.png",
            "date_modified": "2026-05-16T00:00:00.000Z",
            "date_published": "2026-05-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort",
            "content_html": "<p>Version an API only when the old and new semantics cannot safely coexist.\nMost breaking changes are avoidable: add a new field or a new operation next to the old one, leave shipped behavior alone, and keep new request data optional.\nThen deprecate the old shape with runtime headers and usage telemetry before removing it.</p>\n<p>I've written before about implementing <a href=\"https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore\"><strong>API versioning</strong></a> in ASP.NET Core.</p>\n<p>But the more important question isn't <em>how</em> to version an API.\nIt's <em>when</em>.</p>\n<p>Every API team eventually reaches for the same escape hatch:</p>\n<blockquote>\n<p>Just create <code>v2</code>.</p>\n</blockquote>\n<p>It sounds responsible.\nExcept now you maintain two APIs, two sets of docs, two behaviors, and a migration project clients will postpone for as long as possible.</p>\n<p>I also touched on this briefly in <a href=\"https://milanjovanovic.tech/blog/the-5-most-common-rest-api-design-mistakes-and-how-to-avoid-them\"><strong>my REST API design mistakes article</strong></a>, but I want to make the point more directly today:</p>\n<p><strong>Versioning is a compatibility tool. It is not a design strategy.</strong></p>\n<p>Most API changes do not require a new version.\nThey require better change management.</p>\n<p>And that distinction matters.</p>\n<p>If you treat every contract change as a versioning problem, you end up cloning APIs.\nIf you treat it as a change management problem, you start asking better questions:</p>\n<ul>\n<li>Can I add instead of replace?</li>\n<li>Can old and new behavior coexist for a while?</li>\n<li>Can I introduce a new operation instead of mutating an old one?</li>\n<li>Can I deprecate this safely with telemetry and a migration path?</li>\n</ul>\n<p>That mindset leads to APIs that age much better.</p>\n<h2>What Actually Breaks Clients?</h2>\n<p>Breaking changes usually aren't about the URL alone.</p>\n<p>Clients break when you:</p>\n<ul>\n<li>Remove or rename fields</li>\n<li>Change the meaning of existing data</li>\n<li>Tighten request validation</li>\n<li>Change pagination or error formats</li>\n<li>Assume enum-like values are closed forever</li>\n</ul>\n<p>This breaks a client just as surely as deleting an endpoint:</p>\n<pre><code class=\"language-json\">// Before\n{ &quot;total&quot;: 100 }\n\n// After\n{ &quot;total&quot;: { &quot;amount&quot;: 100, &quot;currency&quot;: &quot;USD&quot; } }\n</code></pre>\n<p>You didn't change the path.\nYou didn't rename the endpoint.\nYou still broke clients.</p>\n<p>So instead of asking, &quot;Should this be v2?&quot;, ask, &quot;Can the old and new contract safely coexist?&quot;</p>\n<p>I'll use a simple <code>orders</code> API as the running example for the rest of the article.</p>\n<h2>The Compatibility Rules</h2>\n<p>When I want an API to age well, I keep four rules in mind:</p>\n<ul>\n<li>Keep existing fields and behavior in place</li>\n<li>Don't turn optional request data into required data</li>\n<li>Don't change what an existing operation does</li>\n<li>Make anything new additive and optional by default</li>\n</ul>\n<p>These map directly onto the four rules from Z. Nemec's <a href=\"https://medium.com/good-api/api-change-management-2fe5bba32e9b\">API Change Management</a> article: don't take anything away, don't change processing rules, don't make optional things required, and anything you add must be optional.</p>\n<p>If you follow those rules, many &quot;versioning problems&quot; turn back into ordinary contract evolution.</p>\n<h2>1. Add, Don't Replace</h2>\n<p>The safest change is usually an additive one.</p>\n<p>Let's say your original <code>GET /orders/{id}</code> response looked like this:</p>\n<pre><code class=\"language-json\">{\n  &quot;id&quot;: &quot;ord_123&quot;,\n  &quot;status&quot;: &quot;paid&quot;,\n  &quot;total&quot;: 100\n}\n</code></pre>\n<p>Instead of replacing <code>total</code>, add a new field:</p>\n<pre><code class=\"language-json\">{\n  &quot;id&quot;: &quot;ord_123&quot;,\n  &quot;status&quot;: &quot;paid&quot;,\n  &quot;total&quot;: 100,\n  &quot;totalMoney&quot;: {\n    &quot;amount&quot;: 100,\n    &quot;currency&quot;: &quot;USD&quot;\n  }\n}\n</code></pre>\n<p>Existing clients keep using <code>total</code>.\nNew clients can migrate to <code>totalMoney</code>.\nYou mark the old field as deprecated and remove it only after a real migration window.</p>\n<p>The same idea applies beyond fields.\nIf you need richer semantics, don't mutate a field into a different shape.\nAdd a new field, a new link, or a new operation that carries the new meaning explicitly.</p>\n<p>Sometimes an ugly contract is the price of compatibility.</p>\n<h2>2. Make Clients Tolerant Readers</h2>\n<p>A well-behaved client should not explode because the server added a field it doesn't understand.</p>\n<p>If the response evolves from this:</p>\n<pre><code class=\"language-json\">{\n  &quot;id&quot;: &quot;ord_123&quot;,\n  &quot;status&quot;: &quot;paid&quot;\n}\n</code></pre>\n<p>to this:</p>\n<pre><code class=\"language-json\">{\n  &quot;id&quot;: &quot;ord_123&quot;,\n  &quot;status&quot;: &quot;paid&quot;,\n  &quot;estimatedDeliveryDate&quot;: &quot;2026-05-29&quot;\n}\n</code></pre>\n<p>older clients should ignore the extra property and keep working.</p>\n<p>In .NET, <code>System.Text.Json</code> helps because unknown properties are ignored by default.\nThe real risk is usually overly strict generated SDKs or contract tests that assert exact JSON equality.</p>\n<p>This is one of the most common self-inflicted problems I see.\nTeams say they want backward compatibility, then generate client models that reject any unexpected field in the response.</p>\n<p>That is not a compatibility strategy.\nThat is a trap.</p>\n<p>Your server should be free to add optional data.\nYour clients should be resilient enough to ignore what they don't understand.</p>\n<h2>3. Don't Change What an Existing Operation Does</h2>\n<p>Fields and shapes get most of the attention in compatibility discussions, but the most dangerous breaking changes hide in <strong>behavior</strong>.</p>\n<p>The URL is the same.\nThe request body is the same.\nThe response shape is the same.\nWhat the operation <em>does</em> on the server is different.</p>\n<p>Take <code>DELETE /orders/{id}</code>.</p>\n<p>When the API shipped, that endpoint was a soft delete.\nThe order moved into an <code>archived</code> state, stayed in the database, still showed up in audit reports, and could be restored by support.</p>\n<p>The contract that clients built on wasn't just the HTTP verb and the path.\nIt was the full behavior:</p>\n<ul>\n<li>The order is recoverable</li>\n<li>Related invoices and shipments are untouched</li>\n<li>Audit history is preserved</li>\n<li>The same call is safe to retry</li>\n</ul>\n<p>Months later, the team decides soft-delete is messy.\nThe &quot;fix&quot; turns <code>DELETE /orders/{id}</code> into a hard delete:</p>\n<ul>\n<li>The order row is gone</li>\n<li>Related invoices cascade or get orphaned</li>\n<li>Audit history loses references</li>\n<li>Retrying after a network blip can wipe the wrong record</li>\n</ul>\n<p>No client noticed at code-review time.\nThe SDK call still compiles.\nThe response is still <a href=\"https://milanjovanovic.tech/blog/rest-api-http-status-codes\"><strong><code>204 No Content</code></strong></a>.\nA support tool that used to call <code>DELETE</code> and then &quot;undo&quot; it now silently destroys data.</p>\n<p>This is exactly the kind of change Z. Nemec's rules call out:\n<strong>you must not change the processing rules of an existing operation.</strong>\nOnce clients have integrated, the behavior <em>is</em> the contract, even if it was never written down anywhere.</p>\n<p>The same pattern shows up in subtler ways:</p>\n<ul>\n<li><code>POST /orders</code> used to be idempotent on a client-supplied key, then quietly stops being</li>\n<li><code>POST /orders/{id}/cancel</code> used to refund automatically, then stops issuing refunds because &quot;refunds should be a separate call&quot;</li>\n<li><code>PUT /orders/{id}</code> used to be a full replace, then becomes a partial merge</li>\n<li>A webhook used to fire once per order, now fires per line item</li>\n</ul>\n<p>Each of these keeps the URL, the verb, and the JSON shape stable.\nEach one breaks every existing integration in a way that won't show up in a schema diff.</p>\n<p>The safe move is the same as before: <strong>add, don't mutate.</strong></p>\n<p>If you want a hard delete, expose it as a new operation and leave the old one alone:</p>\n<pre><code class=\"language-http\">DELETE /orders/{id}            # still soft-delete, unchanged\nDELETE /orders/{id}?purge=true # new, opt-in hard delete\n</code></pre>\n<p>Or introduce a new resource entirely (<code>DELETE /orders/{id}/purge</code>) so the destructive behavior has its own name and its own permissions.</p>\n<p>The rule is simple: <strong>once an operation ships, its behavior is part of the contract.</strong>\nYou can add new operations next to it.\nYou can deprecate it.\nYou cannot quietly change what it does.</p>\n<h2>4. Be Very Careful With Validation</h2>\n<p>This one is underrated.</p>\n<p>There are two flavors of the same mistake:</p>\n<ul>\n<li>Taking an existing optional field and making it required</li>\n<li>Adding a brand-new field and making it required from day one</li>\n</ul>\n<p>Both break older clients in exactly the same way.\nThe endpoint path doesn't move, but requests that used to succeed now get rejected.</p>\n<p>Here's a simple example using <code>POST /orders</code>.</p>\n<p>Yesterday this request was valid:</p>\n<pre><code class=\"language-json\">{\n  &quot;customerId&quot;: &quot;cus_123&quot;,\n  &quot;currency&quot;: &quot;USD&quot;\n}\n</code></pre>\n<p>Today the API requires a country for tax calculation:</p>\n<pre><code class=\"language-json\">{\n  &quot;customerId&quot;: &quot;cus_123&quot;,\n  &quot;currency&quot;: &quot;USD&quot;,\n  &quot;country&quot;: &quot;US&quot;\n}\n</code></pre>\n<p>Whether <code>country</code> was previously optional or didn't exist at all, the result is the same: every existing integration starts failing at runtime.</p>\n<p>A safer path is to accept missing values for older clients, infer defaults where possible, or introduce a new operation for the stricter workflow.</p>\n<p>For example:</p>\n<ul>\n<li>Accept missing <code>country</code> during a transition window</li>\n<li>Infer it from an existing billing profile if you can</li>\n<li>Add a new <code>POST /checkout-sessions</code> flow that requires the richer request model</li>\n</ul>\n<p>Response changes usually get careful design review.\nRequest validation changes deserve the same scrutiny.\nAnd the underlying rule is the one that catches both flavors: <strong>anything you add to the contract has to be optional, and anything that was optional has to stay optional.</strong></p>\n<h2>A New Operation Is Often Cheaper Than a New Version</h2>\n<p>Sometimes the use case really did change enough that piling more flags and optional parameters onto an existing endpoint becomes confusing.</p>\n<p>This is what a bad evolution path looks like:</p>\n<pre><code class=\"language-http\">POST /orders?validateOnly=true&amp;includeTaxEstimate=true&amp;reserveInventory=true\n</code></pre>\n<p>At that point you don't have one clean operation.\nYou have multiple workflows hiding behind one endpoint.</p>\n<p>That's when I prefer a new operation or resource over a whole API version.</p>\n<pre><code class=\"language-http\">POST /orders\nPOST /orders/quote\nPOST /checkout-sessions\n</code></pre>\n<p>This keeps the old contract stable while giving the new behavior a clean home.</p>\n<p><code>POST /orders</code> stays the simple &quot;place an order&quot; endpoint.\n<code>POST /orders/quote</code> becomes the &quot;tell me what this would cost&quot; operation.\n<code>POST /checkout-sessions</code> can support a richer, more guided flow without contaminating the original contract.</p>\n<p>That is usually much cheaper than creating <code>/v2/orders</code> and dragging the rest of your API along with it.</p>\n<h2>Deprecate Like You Mean It</h2>\n<p>This is the missing half of API change management.</p>\n<p>Most deprecations are fake.\nThey exist in docs, but nothing operational happens.</p>\n<p>A real deprecation process should include four things:</p>\n<ol>\n<li>Mark the old field or endpoint as deprecated in your OpenAPI description.</li>\n<li>Signal the deprecation at runtime.</li>\n<li>Give consumers a migration path.</li>\n<li>Measure actual usage before removing anything.</li>\n</ol>\n<p>If you're on HTTP, runtime signaling can be as simple as response headers like these:</p>\n<pre><code class=\"language-http\">Deprecation: true\nSunset: Wed, 31 Dec 2026 23:59:59 GMT\nLink: &lt;https://docs.example.com/migrations/orders-total&gt;; rel=&quot;deprecation&quot;\n</code></pre>\n<p>Now the deprecation is visible in the docs, visible in live traffic, and connected to an actual migration guide.</p>\n<p>And this is where telemetry matters.\nIf you don't know which clients still use the deprecated field or endpoint, you are not managing change.\nYou are guessing.</p>\n<p>Track usage by client ID, API key, tenant, or application name.\nThen wait until usage is effectively gone before removing anything.</p>\n<h2>When Versioning Is Actually The Right Call</h2>\n<p>I am not anti-versioning.</p>\n<p>Version when the old and new semantics cannot coexist safely.\nVersion when the resource model changed fundamentally.\nVersion when compatibility rules would force you into a contract nobody can reason about.</p>\n<p>In those cases, version deliberately.</p>\n<p>And deliberate versioning means choosing the smallest break you can justify.</p>\n<p>Sometimes that's a new endpoint shape.\nSometimes it's a representation variant.\nSometimes, especially for public APIs, it's straightforward URL versioning because it is explicit and easy to communicate.</p>\n<p>The key is not which mechanism you pick.\nThe key is that you reached for it because coexistence failed, not because it was the first idea on the table.</p>\n<p>And if you do version, pair it with an actual deprecation process:</p>\n<ul>\n<li>Mark old fields or endpoints as deprecated</li>\n<li>Communicate a removal date</li>\n<li>Give clients migration examples</li>\n<li>Monitor usage before removing anything</li>\n</ul>\n<p>The real work is not creating <code>v2</code>.\nThe real work is getting consumers off <code>v1</code>.</p>\n<h2>Takeaway</h2>\n<p>The best API version is often the one you never have to create.</p>\n<p>If you want a simple decision rule, use this:</p>\n<ol>\n<li>Can I add instead of replace?</li>\n<li>Can old and new contracts coexist during a migration window?</li>\n<li>Can I introduce a new operation instead of mutating an old one?</li>\n<li>Can I deprecate the old shape with docs, headers, and telemetry?</li>\n</ol>\n<p>If the answer is yes, you probably don't need a new version.</p>\n<p>If the answer is no, and the old and new worlds genuinely cannot live side by side, version deliberately.</p>\n<p>That's the real point.</p>\n<p>Design contracts to evolve.\nTreat clients as long-lived integrations, not just today's code.\nAnd reserve versioning for the cases where compatibility truly runs out.</p>\n<p>If you want to go deeper on designing and evolving HTTP APIs, check out <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a>.\nIt's where I cover the patterns, trade-offs, and implementation details I use when building APIs that need to survive real clients and real change.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort",
            "title": "API Versioning Should Be Your Last Resort",
            "summary": "Most teams reach for v2 too early because they don't have a contract evolution strategy. Here's the API change management approach I prefer: evolve contracts…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_193.png",
            "date_modified": "2026-05-09T00:00:00.000Z",
            "date_published": "2026-05-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them",
            "content_html": "<p>An invariant is a business rule that an object must satisfy at all times, not only at the point where some handler or validator checks it.\nThe cleanest place to enforce one is the domain model: block construction of invalid objects, put state changes behind methods that own the rules, and keep aggregate-wide rules on the root.</p>\n<p>A lot of the &quot;DDD-ish&quot; .NET code I review scatters business rules across handlers, validators,\nand controllers, and barely puts any on the domain model itself.</p>\n<p>Each copy of the same rule drifts a little over time, and whether a given object is <em>valid</em> starts to depend on which path the caller took to reach it.</p>\n<p>You can absolutely build a working system this way.\nI've shipped plenty of procedural code with enough <code>if</code>-checks to keep things in line.\nBut there's a cleaner way to think about it, and it starts with a single idea.</p>\n<h2>What Is an Invariant?</h2>\n<p>An <strong>invariant</strong> is a rule about an object that must hold true for as long as the object exists.</p>\n<p>Not just when you save it, or when a validator happens to run.\nThe rule has to hold every time you touch the object, no matter how it got into memory.</p>\n<p>A few examples:</p>\n<ul>\n<li>A <code>Course</code> always has a non-empty title.</li>\n<li>An <code>Order</code> total always equals the sum of its line items.</li>\n<li>A <code>Subscription</code> is in exactly one state: <code>Trial</code>, <code>Active</code>, <code>PastDue</code>, or <code>Canceled</code>.</li>\n<li>A published course has at least one lesson.</li>\n</ul>\n<p>None of these mention validation, persistence, or HTTP.\nThey're statements about the <em>domain</em>, and they should be true regardless of how the object got loaded.</p>\n<h2>Where Procedural Code Goes Wrong</h2>\n<p>Take a simple <code>Course</code> written the way most CRUD-ish .NET apps still write it:</p>\n<pre><code class=\"language-csharp\">public class Course\n{\n    public string Title { get; set; }\n    public CourseStatus Status { get; set; }\n    public DateTime? PublishedOn { get; set; }\n    public decimal Price { get; set; }\n}\n</code></pre>\n<p>There's no constructor and every property has a public setter, so the class is willing to accept any combination of values.</p>\n<p>To keep the data correct, the rules end up scattered across the application:</p>\n<ul>\n<li><code>CreateCourseValidator</code> checks the title isn't empty.</li>\n<li><code>PublishCourseHandler</code> sets <code>Status</code> and <code>PublishedOn</code>, and remembers to check the course isn't already published.</li>\n<li><code>ChangePriceHandler</code> checks the course isn't archived.</li>\n<li>A new endpoint shows up, someone copies an existing handler, and the archive check quietly goes missing.</li>\n</ul>\n<p>Every rule lives in a place that just happens to be on the path the request took.\nNothing on the <code>Course</code> itself prevents it from drifting into an invalid state.</p>\n<p>That's the real cost of an <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>anemic model</strong></a>.\nIt isn't that there's no behavior on the class.\nIt's that the class makes no promises, so every caller has to enforce the rules itself.</p>\n<h2>Always-Valid: The Model as the Source of Truth</h2>\n<p>The shift I want to make is simple: the model never accepts an invalid state.</p>\n<p>If you're holding a <code>Course</code> reference, you can trust it.\nYou don't need an <code>if (course.Title is null)</code> somewhere down the call stack, you don't need a parallel validator double-checking, and you don't need to hope the handler remembered the right guard.</p>\n<p>Three moves get you there.</p>\n<h3>1. Block construction of invalid objects</h3>\n<p>A <code>Course</code> without a title shouldn't exist, so the easiest fix is to make it impossible to construct.</p>\n<pre><code class=\"language-csharp\">public class Course\n{\n    private Course(CourseId id, string title, Money price)\n    {\n        Id = id;\n        Title = title;\n        Price = price;\n        Status = CourseStatus.Draft;\n    }\n\n    public static Result&lt;Course&gt; Create(string title, Money price)\n    {\n        if (string.IsNullOrWhiteSpace(title))\n        {\n            return CourseErrors.TitleRequired;\n        }\n\n        return new Course(CourseId.New(), title, price);\n    }\n}\n</code></pre>\n<p>A private constructor with a static factory gives you a single, well-known place where a <code>Course</code> can come into existence, and that's where the validation runs.\nFrom that point on, any code holding a <code>Course</code> reference can assume it has a valid title.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals\"><strong>Value objects</strong></a> like <code>Money</code> apply the same idea at a smaller scope, so a <code>Money</code> can't be negative or be missing its currency by the time you're using it.</p>\n<h3>2. Encapsulate state transitions</h3>\n<p>Once construction is locked down, the next leak is state changes.\nThe class should control how it changes, instead of leaving that up to whoever has a reference to it.\nNo setters, and every change goes through a method that knows the rules.</p>\n<pre><code class=\"language-csharp\">public Result Publish(IDateTimeProvider clock)\n{\n    if (Status != CourseStatus.Draft)\n    {\n        return CourseErrors.AlreadyPublished;\n    }\n\n    if (_lessons.Count == 0)\n    {\n        return CourseErrors.CannotPublishWithoutLessons;\n    }\n\n    Status = CourseStatus.Published;\n    PublishedOn = clock.UtcNow;\n    return Result.Success();\n}\n</code></pre>\n<p>The handler doesn't need to know whether the course was already published, and it doesn't need to remember to check for empty lessons.\nIt calls <code>Publish</code> and propagates whatever result comes back.\nThe rule lives next to the state it protects, in one place.</p>\n<h3>3. Encapsulate the aggregate</h3>\n<p>Some rules span multiple entities inside the same boundary.\nThe aggregate root is the right place to enforce those, because it's the transactional boundary.</p>\n<p>Take this rule: a published course must have at least one lesson, and lessons can't be removed once it's published.</p>\n<p>The wrong way is to expose <code>Lessons</code> as a mutable collection and rely on the application service to remember the rule everywhere it's used.\nThe right way is to keep the collection private and force every change through the root:</p>\n<pre><code class=\"language-csharp\">public sealed class Course\n{\n    private readonly List&lt;Lesson&gt; _lessons = [];\n    public IReadOnlyCollection&lt;Lesson&gt; Lessons =&gt; _lessons.AsReadOnly();\n\n    public Result RemoveLesson(LessonId id)\n    {\n        if (Status == CourseStatus.Published)\n        {\n            return CourseErrors.CannotModifyPublishedLessons;\n        }\n\n        var lesson = _lessons.FirstOrDefault(l =&gt; l.Id == id);\n        if (lesson is null)\n        {\n            return CourseErrors.LessonNotFound;\n        }\n\n        _lessons.Remove(lesson);\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>When a rule needs to span <em>two</em> aggregates instead of one, that's a different problem, and I'd reach for a <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>domain event</strong></a> rather than letting one aggregate reach into another.</p>\n<h2>What You Actually Get</h2>\n<p>You can write the same system procedurally, and it can work well.\nThe thing you give up is <strong>trust</strong>.</p>\n<p>In a procedural system, every caller shares responsibility for not breaking the rules.\nIn an always-valid model, that responsibility lives on the domain model.\nThe difference compounds over time:</p>\n<ul>\n<li>Validators don't drift, because there's nothing to duplicate.</li>\n<li>Code reviews focus on behavior instead of &quot;did we forget a check?&quot;.</li>\n<li>New endpoints can't accidentally bypass a rule that lives on the entity.</li>\n<li>Tests stop covering scenarios that aren't even expressible.</li>\n</ul>\n<p>The model goes from being a passive data carrier to being the smallest, sharpest place where the business rules live.</p>\n<p>Fundamentally, this is about <strong>encapsulation</strong>.\nThe model encapsulates the rules that govern its state, and the rest of the system interacts with it through a well-defined interface.\nThat leads to cleaner code, fewer bugs, and a more maintainable system overall.</p>\n<h2>Summary</h2>\n<p>An invariant is a rule that must always hold true while the object exists, and the cleanest place to enforce it is on the object itself.</p>\n<ul>\n<li><strong>Construction invariants</strong> belong in a private constructor behind a factory.</li>\n<li><strong>State transition invariants</strong> belong in methods that own the state they change.</li>\n<li><strong>Aggregate-wide invariants</strong> belong on the root, with child entities accessed only through it.</li>\n</ul>\n<p>The tradeoff is that you give up the freedom to write procedural code that could be easier to understand in the short term,\nbut you get a model that you can trust to always be valid.</p>\n<p>If you want to go deeper into modeling aggregates, value objects, and rich behavior across a real system,\nI think you will enjoy <a href=\"https://milanjovanovic.tech/pragmatic-domain-driven-design\"><strong>Pragmatic Domain-Driven Design</strong></a>.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them",
            "title": "What Invariants Are (and Why a Domain Model Is the Best Place to Enforce Them)",
            "summary": "Most 'DDD-ish' code I review enforces business rules everywhere except in the model itself, so the same rule ends up duplicated across handlers and validators.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_192.png",
            "date_modified": "2026-05-02T00:00:00.000Z",
            "date_published": "2026-05-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-test-pyramid-is-a-lie-and-what-i-do-instead",
            "content_html": "<p>The test pyramid assumes integration tests are slow and expensive, which stopped being true once Testcontainers and the Aspire test host arrived.\nI ship 15-25% unit tests on pure domain logic, 60-70% integration tests against real infrastructure, and under 10% end-to-end tests for business-critical flows.</p>\n<p>I'll be honest. For years, my projects didn't look like the <strong>test pyramid</strong>.</p>\n<p>A wide base of unit tests, a narrow middle of integration tests, a tiny sliver of end-to-end tests at the top.\nI'd nod along in conference talks, then go back to my own code and do something different.</p>\n<p>A thin layer of unit tests for the things worth unit-testing.\nA thick slab of integration tests against real PostgreSQL, real RabbitMQ, real HTTP.\nA handful of end-to-end tests for the flows that would get me fired if they broke.</p>\n<p>And I shipped with more confidence that way, not less.\nHere's why, and here's the shape I actually use.</p>\n<h2>Where the Pyramid Comes From</h2>\n<p>The pyramid was popularized by Mike Cohn in 2009, when integration tests meant a shared database server, flaky CI, and 20-minute builds.\nUnit tests with mocks were the pragmatic compromise.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_191/test_pyramid.png\" alt=\"The classic test pyramid, with a wide base of unit tests, a narrow middle of integration tests, and a tiny sliver of end-to-end tests at the top.\">\n<p>That world is gone.\nWith <a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>Testcontainers</strong></a>, I can spin up PostgreSQL, Redis, and RabbitMQ in a fresh container per test class in a few seconds.\nThe <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/testing/overview\"><strong>Aspire test host</strong></a> takes this further by wiring up your entire application graph.\nThe argument that real dependencies are too expensive mostly doesn't apply anymore.</p>\n<p>But the advice didn't update.</p>\n<h2>The Bug That Convinced Me</h2>\n<p>A few years ago I had a service with <strong>94% unit test coverage</strong>. All green.</p>\n<p>A user reported that deleting an account didn't actually delete their data.\nThe bug was three lines long:</p>\n<pre><code class=\"language-csharp\">public async Task Handle(DeleteAccountCommand command, CancellationToken ct)\n{\n    var account = await _repository.GetByIdAsync(command.AccountId, ct);\n    account.MarkAsDeleted();\n    // Missing: await _unitOfWork.SaveChangesAsync(ct);\n}\n</code></pre>\n<p>The honest diagnosis is that the test for this case was never written.\nYou can absolutely verify <code>SaveChangesAsync</code> was called with a mock.\nBut in a codebase where that test is <em>one of hundreds</em> of handler tests, each with its own mock setup and its own verification list, it's the kind of assertion people forget.\nI forgot.</p>\n<p>A single integration test against a real database would have caught it without anyone having to remember.\nThat's the point: the fewer invariants your test style forces you to remember, the fewer bugs slip through.</p>\n<p>That was the last week I took the test pyramid seriously.</p>\n<h2>What Unit Tests Are Actually Good At</h2>\n<p>I still write <a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>unit tests</strong></a>. Just not many.</p>\n<p>They earn their keep when the logic is non-trivial, pure (no I/O, no time, no randomness), and hard to exercise end-to-end.\nThat's a specific set of code: <a href=\"https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals\"><strong>value objects</strong></a> and <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>rich domain models</strong></a>, pricing and tax calculations, parsers, mappers, serializers.</p>\n<p>Notice what's <strong>not</strong> on that list: application services, handlers, controllers, repositories, infrastructure.\nThose live at the seams, and the seams are where real bugs live.</p>\n<h2>What I Actually Write Instead</h2>\n<p>Here's the shape I've settled on for a typical .NET service or <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>.</p>\n<h3>Layer 1: A thin base of unit tests</h3>\n<p>Maybe 15-25% of the test count. All domain logic.\nNo mocks of collaborators. If a unit test needs a mock, I usually pull the test up to the integration layer instead.</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void Confirm_WhenPending_TransitionsToConfirmed()\n{\n    var order = Order.Create(CustomerId.New(), Money.Usd(100));\n\n    order.Confirm();\n\n    order.Status.Should().Be(OrderStatus.Confirmed);\n    order.DomainEvents.Should().ContainSingle(e =&gt; e is OrderConfirmedEvent);\n}\n</code></pre>\n<p>No container or mocks.\nMicroseconds per test.\nThis is what unit tests are for.</p>\n<h3>Layer 2: A thick middle of integration tests</h3>\n<p>The majority. Maybe 60-70% of the suite.\nEvery <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\"><strong>command and query handler</strong></a>, every HTTP endpoint, every message consumer gets a test that runs against <strong>real infrastructure</strong> inside Testcontainers.\nIn a <a href=\"https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing\"><strong>modular monolith</strong></a>, this is where you verify that modules talk to each other correctly across their public APIs.</p>\n<pre><code class=\"language-csharp\">public class DeleteAccountTests(IntegrationTestWebAppFactory factory)\n    : BaseIntegrationTest(factory)\n{\n    [Fact]\n    public async Task DeleteAccount_WhenAccountExists_MarksAccountAsDeleted()\n    {\n        var account = await CreateAccountAsync();\n\n        var response = await HttpClient.DeleteAsync($&quot;/accounts/{account.Id}&quot;);\n\n        response.StatusCode.Should().Be(HttpStatusCode.NoContent);\n\n        var stored = await DbContext.Accounts\n            .IgnoreQueryFilters()\n            .SingleAsync(a =&gt; a.Id == account.Id);\n\n        stored.IsDeleted.Should().BeTrue();\n    }\n}\n</code></pre>\n<p>That test exercises the HTTP layer, routing, model binding, authorization, the handler, the unit of work, EF Core, and PostgreSQL.\nIt proves the thing you actually care about: <strong>when I call this endpoint, the row changes.</strong>\nAnd it does it without anyone having to remember to assert <code>SaveChangesAsync</code> was called.</p>\n<h3>Layer 3: A small cap of end-to-end tests</h3>\n<p>Under 10%. Only the flows where a silent failure would be a commercial or compliance problem.\nSignup, payment, refund, password reset, <a href=\"https://milanjovanovic.tech/blog/how-to-implement-two-factor-authentication-in-aspnetcore\"><strong>two-factor enrollment</strong></a>.\nThey're slow and occasionally flaky, but they catch the one failure mode everything else misses: <em>the system as a whole still works</em>.</p>\n<h3>Layer 0: Architecture and contract tests</h3>\n<p>Often forgotten, but they're part of the suite.\n<a href=\"https://milanjovanovic.tech/blog/5-architecture-tests-you-should-add-to-your-dotnet-projects\"><strong>Architecture tests</strong></a> enforce layering and module boundaries.\nContract tests verify that message schemas and API shapes don't drift.\nThey run in milliseconds and catch the &quot;six months from now, someone will break this without realizing&quot; kind of bug.</p>\n<p>The shape that comes out of this is closer to Kent C. Dodds' <strong>testing trophy</strong> than a pyramid.\nThe fat middle is deliberate. That's where my confidence comes from.</p>\n<h2>The Usual Objections</h2>\n<p><strong>&quot;Integration tests are slow.&quot;</strong>\nMy typical integration suite runs in 2-4 minutes in CI with Testcontainers reuse and test-class parallelization.\nSlower than unit tests, yes. Faster than finding the bug in production.</p>\n<p><strong>&quot;Mocks are fine if you're disciplined.&quot;</strong>\nMaybe. But every large codebase I've audited that leaned heavily on mocks had the same pathology: tests that pass after a refactor even though the refactor broke production.\nThat's not discipline failing. That's the tool being pointed in the wrong direction.</p>\n<h2>Summary</h2>\n<p>The test pyramid was good advice for 2009 infrastructure and poor advice for 2026 infrastructure.\nTestcontainers and Aspire changed the economics, and the fastest feedback loop that still tells you the truth is now an integration test against real dependencies.\nUnit tests still belong on pure domain logic. Everything at the seams belongs in the integration layer.</p>\n<p>If you want to see how I wire this into a real system, with the <a href=\"https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing\"><strong>integration test harness</strong></a>, module boundaries, and the full <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture</strong></a> setup, check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.\nIt's the same approach I use on my own projects.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-test-pyramid-is-a-lie-and-what-i-do-instead",
            "title": "The Test Pyramid Is a Lie (and What I Do Instead)",
            "summary": "The test pyramid made sense when integration tests meant a shared database server and a 20-minute build. It doesn't match how I build .NET systems in 2026.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_191.png",
            "date_modified": "2026-04-25T00:00:00.000Z",
            "date_published": "2026-04-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/why-i-switched-to-primary-constructors-for-di-in-csharp",
            "content_html": "<p>Primary constructors let you declare dependencies on the class declaration and use them anywhere in the class body, which removes the fields, the constructor, and the assignments from a typical service class.\nThe catch is that captured parameters are mutable variables, not <code>readonly</code> fields, so nothing stops you from reassigning one.</p>\n<p>I'll be honest. I resisted primary constructors for a while.</p>\n<p>When C# 12 extended them from <a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong><code>record</code> types</strong></a> to regular classes and structs, my first reaction was skepticism.\nAn implicit mutable capture instead of explicit <code>readonly</code> fields?\nThat felt like trading safety for convenience.</p>\n<p>But after using them across several projects, I changed my mind.\nThe boilerplate they eliminate in DI service classes is significant, and the pitfall I was worried about is manageable once you know about it.</p>\n<p>Here's what convinced me to switch, and the one thing you need to watch out for.</p>\n<h2>What Changed My Mind</h2>\n<p>Here's what my service classes used to look like:</p>\n<pre><code class=\"language-csharp\">public class OrderService\n{\n    private readonly IOrderRepository _orderRepository;\n    private readonly ILogger&lt;OrderService&gt; _logger;\n\n    public OrderService(\n        IOrderRepository orderRepository,\n        ILogger&lt;OrderService&gt; logger)\n    {\n        _orderRepository = orderRepository;\n        _logger = logger;\n    }\n\n    public async Task&lt;Order?&gt; GetOrderAsync(Guid id)\n    {\n        _logger.LogInformation(&quot;Fetching order {OrderId}&quot;, id);\n\n        return await _orderRepository.GetByIdAsync(id);\n    }\n}\n</code></pre>\n<p>And here's what they look like now:</p>\n<pre><code class=\"language-csharp\">public class OrderService(\n    IOrderRepository orderRepository,\n    ILogger&lt;OrderService&gt; logger)\n{\n    public async Task&lt;Order?&gt; GetOrderAsync(Guid id)\n    {\n        logger.LogInformation(&quot;Fetching order {OrderId}&quot;, id);\n\n        return await orderRepository.GetByIdAsync(id);\n    }\n}\n</code></pre>\n<p>The field declarations, the constructor body, the assignments. All gone.\nThe parameters are captured and available throughout the class body.</p>\n<p>This is the most common use case for primary constructors: <a href=\"https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor\"><strong>dependency injection</strong></a> in service classes.\nYou declare what you need, and use it directly.</p>\n<h2>Where I Use Them Most: DI Service Classes</h2>\n<p>The place where primary constructors sold me is ASP.NET Core service classes.\nThis is where I spend most of my time, and the boilerplate savings add up fast.</p>\n<p>Here's a more realistic example from a checkout flow:</p>\n<pre><code class=\"language-csharp\">public class CheckoutService(\n    IPaymentProcessor paymentProcessor,\n    IOrderRepository orderRepository,\n    ILogger&lt;CheckoutService&gt; logger,\n    IOptions&lt;CheckoutOptions&gt; options)\n{\n    public async Task&lt;CheckoutResult&gt; ProcessAsync(\n        Cart cart,\n        CancellationToken ct = default)\n    {\n        var settings = options.Value;\n\n        if (cart.Total &lt; settings.MinimumOrderAmount)\n        {\n            logger.LogWarning(&quot;Order below minimum: {Total}&quot;, cart.Total);\n            return CheckoutResult.BelowMinimum;\n        }\n\n        var order = Order.Create(cart);\n\n        await paymentProcessor.ChargeAsync(order, ct);\n        await orderRepository.SaveAsync(order, ct);\n\n        logger.LogInformation(&quot;Checkout complete for order {OrderId}&quot;, order.Id);\n\n        return CheckoutResult.Success;\n    }\n}\n</code></pre>\n<p>Four dependencies, zero boilerplate.\nThe class reads top-to-bottom without any noise.</p>\n<p>This pattern works well because service classes typically don't need to validate or transform their dependencies.\nThe DI container provides them, and you use them.\nPrimary constructors are a perfect fit for this.</p>\n<h2>Entity Construction (With a Caveat)</h2>\n<p>I also started using primary constructors for <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>domain entities</strong></a> and <a href=\"https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals\"><strong>value objects</strong></a> where you want to enforce required parameters at construction time:</p>\n<pre><code class=\"language-csharp\">public class Order(Guid customerId, Money total)\n{\n    public Guid Id { get; } = Guid.NewGuid();\n    public Guid CustomerId { get; } = customerId;\n    public Money Total { get; } = total;\n    public OrderStatus Status { get; private set; } = OrderStatus.Pending;\n    public DateTime CreatedAt { get; } = DateTime.UtcNow;\n\n    public void Confirm()\n    {\n        if (Status != OrderStatus.Pending)\n        {\n            throw new InvalidOperationException(\n                $&quot;Cannot confirm order in {Status} status.&quot;);\n        }\n\n        Status = OrderStatus.Confirmed;\n    }\n}\n</code></pre>\n<p>There's no way to create an <code>Order</code> without a <code>customerId</code> and <code>total</code>.\nThe primary constructor makes this constraint visible at the type declaration level.</p>\n<p>Notice the key difference from the service class pattern: here, I'm assigning primary constructor parameters to <strong>properties with initializers</strong> (<code>= customerId</code>).\nThis is important, and it leads to the biggest pitfall.</p>\n<h2>The Pitfall That Almost Stopped Me</h2>\n<p>This was the reason I held off for so long.</p>\n<p><strong>Primary constructor parameters are not <code>readonly</code> fields.</strong></p>\n<p>When you use a primary constructor parameter directly in the class body (like we did in the service class), the compiler captures it as a <strong>mutable variable</strong>.\nThere's no <code>readonly</code> backing field generated behind the scenes.</p>\n<p>This means you can accidentally reassign a parameter:</p>\n<pre><code class=\"language-csharp\">public class OrderService(\n    IOrderRepository orderRepository,\n    ILogger&lt;OrderService&gt; logger)\n{\n    public async Task&lt;Order?&gt; GetOrderAsync(Guid id)\n    {\n        logger.LogInformation(&quot;Fetching order {OrderId}&quot;, id);\n\n        return await orderRepository.GetByIdAsync(id);\n    }\n\n    public void SomeOtherMethod()\n    {\n        // This compiles. No warning. No error.\n        orderRepository = null!;\n        logger = null!;\n    }\n}\n</code></pre>\n<p>This compiles without any warning.</p>\n<p>With a traditional constructor and <code>private readonly</code> fields, the compiler would stop you immediately.\nWith primary constructors, it stays silent.</p>\n<p><strong>If you need immutability guarantees</strong>, explicitly assign the parameter to a <code>readonly</code> field:</p>\n<pre><code class=\"language-csharp\">public class OrderService(\n    IOrderRepository orderRepository,\n    ILogger&lt;OrderService&gt; logger)\n{\n    private readonly IOrderRepository _orderRepository = orderRepository;\n    private readonly ILogger&lt;OrderService&gt; _logger = logger;\n\n    public async Task&lt;Order?&gt; GetOrderAsync(Guid id)\n    {\n        _logger.LogInformation(&quot;Fetching order {OrderId}&quot;, id);\n\n        return await _orderRepository.GetByIdAsync(id);\n    }\n}\n</code></pre>\n<p>But now you've lost most of the benefit of primary constructors.\nYou're back to field declarations and assignments, just with a different syntax.</p>\n<p>In practice, I've never actually hit this bug in a DI service class.\nYou're unlikely to accidentally reassign <code>logger</code> in the middle of a method.\nBut it <strong>can</strong> bite you in entity classes or value types where immutability actually matters.\nThat's the one place where I still stay cautious.</p>\n<h2>Where I Still Use Traditional Constructors</h2>\n<p>I haven't switched everything over.\nHere are the cases where I stick with the traditional approach:</p>\n<p><strong>Complex validation logic.</strong> If you need to validate parameters before assigning them, you need a constructor body:</p>\n<pre><code class=\"language-csharp\">public class EmailAddress\n{\n    private readonly string _value;\n\n    public EmailAddress(string value)\n    {\n        if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))\n        {\n            throw new ArgumentException(\n                &quot;Invalid email address.&quot;, nameof(value));\n        }\n\n        _value = value;\n    }\n}\n</code></pre>\n<p>Primary constructors don't give you a place to put validation logic before the class body runs.</p>\n<p><strong>Multiple constructor overloads.</strong> Primary constructors support one constructor signature.\nIf you need overloads, you'll have to chain secondary constructors with <code>this(...)</code>, which gets messy fast.</p>\n<p><strong>Too many parameters.</strong> Once you hit 5+ dependencies, the primary constructor line becomes hard to read.\nAt that point, your class probably has too many responsibilities, and <a href=\"https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips\"><strong>refactoring</strong></a> it is a better solution than formatting tricks.</p>\n<h2>Summary</h2>\n<p>Here's what I've settled on after using primary constructors across several projects:</p>\n<ul>\n<li>I use <strong>primary constructors</strong> for all my <strong>DI service classes</strong>. The boilerplate savings are worth it.</li>\n<li>They're useful for <strong>entity construction</strong> when you want to enforce required parameters at the type level.</li>\n<li>Primary constructor parameters are <strong>captured as mutable variables</strong>, not <code>readonly</code> fields. This is the one thing you need to know.</li>\n<li>I'm not afraid of the mutable capture pitfall in service classes, because it's unlikely to cause real bugs in that context.</li>\n<li>I stick with <strong>traditional constructors</strong> for validation-heavy types, multiple overloads, or classes with too many dependencies</li>\n</ul>\n<p>The switch was worth it.\nMy service classes are shorter, easier to scan, and the pitfall is manageable with the right tooling.</p>\n<p>That's all for today.\nSee you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/why-i-switched-to-primary-constructors-for-di-in-csharp",
            "title": "Why I Switched to Primary Constructors for DI in C#",
            "summary": "I resisted primary constructors for a while. They felt like a shortcut that would cost me later.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_190.png",
            "date_modified": "2026-04-18T00:00:00.000Z",
            "date_published": "2026-04-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-wolverine",
            "content_html": "<p>A saga in Wolverine is a class that extends <code>Saga</code> with a <code>Handle</code> method per message type, returning new messages to move the workflow forward.\nWolverine persists the state and correlates each message to the right saga instance by convention.\nA message extending <code>TimeoutMessage</code> schedules the compensation path if the expected event never arrives.</p>\n<p>Long-running business processes don't fit neatly into a single request.</p>\n<p>Think about user onboarding: you register the user, send a verification email, wait for them to verify, and then send a welcome email.\nEach step depends on the previous one.\nIf the user never verifies, you need a way to handle that.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>Saga pattern</strong></a> breaks this into a sequence of steps, each with its own message and handler.\nIf a step fails or times out, the saga runs <a href=\"https://en.wikipedia.org/wiki/Compensating_transaction\">compensation logic</a> instead of leaving the system in a broken state.</p>\n<p>I've covered sagas with <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>MassTransit</strong></a> and <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq\"><strong>Rebus</strong></a> before.\nBoth work well, but their <a href=\"https://en.wikipedia.org/wiki/Finite-state_machine\">state machine</a> DSLs come with a fair amount of ceremony.\nSince <a href=\"https://milanjovanovic.tech/blog/mediatr-and-masstransit-going-commercial-what-this-means-for-you\">MassTransit moved to a commercial license</a>, more teams have been exploring Wolverine as an alternative.</p>\n<p><a href=\"https://wolverinefx.net/\"><strong>Wolverine</strong></a> takes a <a href=\"https://wolverinefx.net/guide/durability/sagas\">different approach</a> - you write a class that extends <code>Saga</code>, define <code>Handle</code> methods for each message type, and <a href=\"https://wolverinefx.net/guide/handlers/cascading\">cascade new messages</a> from return values.\nWolverine handles routing, persistence, and correlation automatically.</p>\n<h2>Configuring Wolverine</h2>\n<p>We need <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>RabbitMQ</strong></a> for message transport and <a href=\"https://www.postgresql.org/\">PostgreSQL</a> for durable saga state and messaging.</p>\n<pre><code class=\"language-csharp\">var connectionString = builder.Configuration.GetConnectionString(&quot;user-mgmt&quot;);\n\nbuilder.Host.UseWolverine(options =&gt;\n{\n    options.UseRabbitMqUsingNamedConnection(&quot;rmq&quot;)\n        .AutoProvision()\n        .UseConventionalRouting();\n\n    options.Policies.DisableConventionalLocalRouting();\n\n    options.PersistMessagesWithPostgresql(connectionString!);\n});\n</code></pre>\n<ul>\n<li><code>AutoProvision</code> creates RabbitMQ exchanges and queues automatically</li>\n<li><code>UseConventionalRouting</code> routes messages to queues based on message type names</li>\n<li><code>DisableConventionalLocalRouting</code> forces all messages through RabbitMQ instead of in-process handling</li>\n<li><a href=\"https://wolverinefx.net/guide/durability/postgresql\"><code>PersistMessagesWithPostgresql</code></a> stores saga state and messages in PostgreSQL. Wolverine uses <a href=\"https://wolverinefx.net/guide/durability/sagas#lightweight-saga-storage\"><strong>lightweight saga storage</strong></a> to create a table per saga type, and the <a href=\"https://wolverinefx.net/guide/durability/\"><strong>durable messaging</strong></a> infrastructure ensures nothing is lost if the process crashes</li>\n</ul>\n<p>Wolverine gives you <strong>three ways to persist saga state</strong>.\n<a href=\"https://wolverinefx.net/guide/durability/sagas#lightweight-saga-storage\"><strong>Lightweight storage</strong></a> (what we're using) serializes saga state as JSON in a per-saga table with zero ORM config.\n<a href=\"https://wolverinefx.net/guide/durability/marten/sagas\"><strong>Marten</strong></a> stores sagas as <a href=\"https://martendb.io/\">Marten</a> documents with <a href=\"https://en.wikipedia.org/wiki/Optimistic_concurrency_control\">optimistic concurrency</a> and strong-typed IDs.\n<a href=\"https://wolverinefx.net/guide/durability/efcore/sagas\"><strong>EF Core</strong></a> maps sagas into a flat, queryable table and lets you commit saga state with other data in a single transaction.\nIf you just need saga state management, lightweight storage is the simplest path.</p>\n<p>Required packages:</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;WolverineFx&quot; Version=&quot;5.16.2&quot; /&gt;\n&lt;PackageReference Include=&quot;WolverineFx.Postgresql&quot; Version=&quot;5.16.2&quot; /&gt;\n&lt;PackageReference Include=&quot;WolverineFx.RabbitMQ&quot; Version=&quot;5.16.2&quot; /&gt;\n</code></pre>\n<h2>The Saga Messages</h2>\n<p>Before building the saga, let's define all the messages it will work with:</p>\n<pre><code class=\"language-csharp\">public record SendVerificationEmail(Guid UserId, string Email);\npublic record VerificationEmailSent(Guid Id);\n\npublic record VerifyUserEmail(Guid Id);\n\npublic record SendWelcomeEmail(Guid UserId, string Email, string FirstName);\npublic record WelcomeEmailSent(Guid Id);\n\npublic record OnboardingTimedOut(Guid Id) : TimeoutMessage(5.Minutes());\n</code></pre>\n<p><code>OnboardingTimedOut</code> extends Wolverine's <a href=\"https://wolverinefx.net/guide/durability/sagas#timeout-messages\"><code>TimeoutMessage</code></a>, which automatically schedules a delayed delivery.\nWhen the saga starts, Wolverine will deliver this message after 5 minutes.\nIf the user hasn't verified by then, the saga compensates.</p>\n<h2>The Saga State Diagram</h2>\n<p>Here's how the saga transitions between states:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_189/saga_pattern_state_diagram.png\" alt=\"Saga pattern state diagram showing message flow from broker to consumer to database and processor.\">\n</div>\n<h2>Building the Saga</h2>\n<p>Here's the complete saga class:</p>\n<pre><code class=\"language-csharp\">public class UserOnboardingSaga : Saga\n{\n    public Guid Id { get; set; }\n    public string Email { get; set; } = string.Empty;\n    public string FirstName { get; set; } = string.Empty;\n    public string LastName { get; set; } = string.Empty;\n    public bool IsVerificationEmailSent { get; set; }\n    public bool IsEmailVerified { get; set; }\n    public bool IsWelcomeEmailSent { get; set; }\n    public DateTime StartedAt { get; set; }\n\n    // Step 1: Start the saga when UserRegistered is published\n    public static (\n        UserOnboardingSaga,\n        SendVerificationEmail,\n        OnboardingTimedOut) Start(\n            UserRegistered @event,\n            ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogInformation(\n            &quot;Starting onboarding for user {UserId}&quot;, @event.Id);\n\n        var saga = new UserOnboardingSaga\n        {\n            Id = @event.Id,\n            Email = @event.Email,\n            FirstName = @event.FirstName,\n            LastName = @event.LastName,\n        };\n\n        return (\n            saga,\n            new SendVerificationEmail(saga.Id, saga.Email),\n            new OnboardingTimedOut(saga.Id));\n    }\n\n    // Step 2: Verification email was sent\n    public void Handle(\n        VerificationEmailSent @event,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogInformation(\n            &quot;Verification email sent for user {UserId}&quot;, Id);\n\n        IsVerificationEmailSent = true;\n    }\n\n    // Step 3: User verified their email\n    public SendWelcomeEmail Handle(\n        VerifyUserEmail command,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogInformation(&quot;Email verified for user {UserId}&quot;, Id);\n\n        IsEmailVerified = true;\n\n        return new SendWelcomeEmail(Id, Email, FirstName);\n    }\n\n    // Step 4: Welcome email sent - onboarding complete\n    public void Handle(\n        WelcomeEmailSent @event,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogInformation(&quot;Onboarding complete for user {UserId}&quot;, Id);\n\n        IsWelcomeEmailSent = true;\n\n        MarkCompleted();\n    }\n\n    // Compensation: timeout handler\n    public void Handle(\n        OnboardingTimedOut timeout,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        if (IsEmailVerified)\n        {\n            logger.LogInformation(\n                &quot;Timeout ignored - email already verified for user {UserId}&quot;,\n                Id);\n            return;\n        }\n\n        logger.LogWarning(\n            &quot;Onboarding timed out for user {UserId} - email not verified&quot;,\n            Id);\n\n        MarkCompleted();\n    }\n\n    // NotFound: messages arriving for completed/deleted sagas\n    public static void NotFound(\n        VerifyUserEmail command,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogWarning(\n            &quot;Verify email received but saga {Id} no longer exists&quot;,\n            command.Id);\n    }\n\n    public static void NotFound(\n        OnboardingTimedOut timeout,\n        ILogger&lt;UserOnboardingSaga&gt; logger)\n    {\n        logger.LogInformation(\n            &quot;Timeout received for already-completed saga {Id}&quot;,\n            timeout.Id);\n    }\n}\n</code></pre>\n<p>A few things worth calling out.</p>\n<p><strong>Starting the saga.</strong> <code>Start</code> is a static factory that returns a tuple: the saga instance, a <code>SendVerificationEmail</code> command, and a <a href=\"https://wolverinefx.net/guide/messaging/message-bus#scheduling-message-delivery-or-execution\">scheduled</a> <code>OnboardingTimedOut</code> message. Wolverine persists the saga and delivers the messages for you.</p>\n<p><strong>Handling messages.</strong> Wolverine <a href=\"https://wolverinefx.net/guide/durability/sagas#saga-message-identity\"><strong>correlates messages</strong></a> to the correct saga instance by looking for a <code>[SagaIdentity]</code> attribute, then <code>{SagaTypeName}Id</code>, then <code>Id</code>. Return <code>void</code> to update state silently, or return a message to cascade a new command.</p>\n<blockquote>\n<p><strong>Warning:</strong> Do not call <code>IMessageBus.InvokeAsync()</code> within a saga handler to execute a command on that same saga. You'll be acting on stale or missing data. Use cascading messages (return values) for subsequent work.</p>\n</blockquote>\n<p><strong>Completing the saga.</strong> <code>MarkCompleted()</code> tells Wolverine to delete the saga state from PostgreSQL.</p>\n<p><strong>Concurrency.</strong> Wolverine applies <a href=\"https://en.wikipedia.org/wiki/Optimistic_concurrency_control\">optimistic concurrency control</a> to saga state by default. If two messages for the same saga arrive at the same time, one succeeds and the other retries automatically.</p>\n<p><strong>Timeout and compensation.</strong> <code>OnboardingTimedOut</code> fires 5 minutes after the saga started. If the user verified, we ignore it. Otherwise, we compensate and end the saga. This is the key advantage over fire-and-forget workflows.</p>\n<p><strong>NotFound handlers.</strong> Static <a href=\"https://wolverinefx.net/guide/durability/sagas#when-sagas-are-not-found\"><code>NotFound</code> methods</a> handle messages for sagas that no longer exist. You <strong>must</strong> have one for any message type that could arrive after the saga is deleted. The timeout <code>NotFound</code> handler matters most: in the happy path, the saga completes before the timeout fires.</p>\n<h2>The Sequence Flow</h2>\n<p>Here's the happy path where the user verifies before the timeout:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_189/saga_pattern_sequence_diagram.png\" alt=\"Saga pattern sequence diagram showing message flow from broker to consumer to database and processor.\">\n<p>If the user never verifies, the <code>VerifyUserEmail</code> message never arrives.\nAfter 5 minutes, <code>OnboardingTimedOut</code> fires and the saga compensates.</p>\n<h2>Summary</h2>\n<p>Wolverine's <code>Saga</code> base class gives you a convention-driven way to implement long-running workflows:</p>\n<ul>\n<li><strong><code>Start</code> methods</strong> create and initialize the saga from a triggering event</li>\n<li><strong><code>Handle</code> methods</strong> process messages and cascade new commands via return values</li>\n<li><strong><code>TimeoutMessage</code></strong> schedules delayed compensation without external schedulers</li>\n<li><strong><code>MarkCompleted()</code></strong> cleans up the saga state when the workflow is done</li>\n<li><strong><code>NotFound</code> handlers</strong> gracefully handle messages for sagas that no longer exist</li>\n</ul>\n<p>The Saga pattern shines when you have multi-step processes with potential failures.\nInstead of hoping everything goes right, you design for the cases where it doesn't.</p>\n<p>What I really like about Wolverine's approach is how little code you need.\nYou skip the state machine DSL and explicit correlation config entirely.</p>\n<p>If you want to go deeper on orchestrating distributed workflows and building real-world sagas,\ncheck out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>Hope this was useful. See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-wolverine",
            "title": "Implementing the Saga Pattern With Wolverine",
            "summary": "Long-running business processes don't fit neatly into a single request. Wolverine's Saga support gives you a convention-based approach to orchestrating…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_189.png",
            "date_modified": "2026-04-11T00:00:00.000Z",
            "date_published": "2026-04-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-the-inbox-pattern-for-reliable-message-consumption",
            "content_html": "<p>Most message brokers deliver at least once, so your consumer will eventually see the same message twice.\nThe Inbox pattern writes every incoming message to an inbox table keyed by message id, so duplicates are ignored on insert.\nA separate processor picks up the unprocessed rows in batches and gives you control over retries.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a> gets a lot of attention, and rightly so.\nBut what about the consumer side?</p>\n<p>Your publisher reliably sends a message.\nThe broker delivers it.\nYour consumer processes it.\nThen something goes wrong. A timeout, a crash, a network blip.\nThe broker <strong>redelivers the same message</strong>.\nYour consumer runs the same logic twice.\nThis is a problem.</p>\n<p>The <strong>Inbox pattern</strong> is the counterpart to the Outbox.\nThe Outbox ensures reliable <em>publishing</em>. The Inbox ensures reliable <em>consumption</em>.\nEach incoming message is processed <strong>exactly once</strong>, even when the broker retries.</p>\n<p>Here's how to implement it.</p>\n<h2>Why You Need an Inbox</h2>\n<p>Most message brokers provide <strong>at-least-once delivery</strong>.\nThe broker guarantees every message will be delivered,\nbut it <strong>doesn't</strong> guarantee each message arrives only once.</p>\n<p>Here's a common failure path:</p>\n<ol>\n<li>The broker delivers a message to your consumer</li>\n<li>Your consumer processes it successfully</li>\n<li>Before the ACK reaches the broker, the connection drops</li>\n<li>The broker assumes the message was lost and redelivers it</li>\n<li>Your consumer processes the same message <strong>twice</strong></li>\n</ol>\n<p>You could make each handler <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>idempotent</strong></a>.\nThat works, but it means every consumer needs to check a deduplication table before doing any work.\nThe Inbox centralizes this into a single mechanism at the infrastructure level.\nI will talk more about the trade-offs between the Inbox and the Idempotent Consumer at the end.</p>\n<p>The idea:</p>\n<ol>\n<li>A message arrives from the broker</li>\n<li>Instead of processing it immediately, <strong>write it to an inbox table</strong></li>\n<li>If the message already exists (duplicate), the write is silently ignored</li>\n<li>A <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>background process</strong></a> reads unprocessed messages and handles them</li>\n</ol>\n<p>This decouples reception from processing.\nThe consumer becomes a thin persistence layer that can't produce duplicates.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_188/inbox_pattern_sequence_diagram.png\" alt=\"Inbox pattern sequence diagram showing message flow from broker to consumer to database and processor.\">\n<h2>Inbox Database Schema</h2>\n<p>The <code>inbox_messages</code> table stores every incoming message:</p>\n<pre><code class=\"language-sql\">CREATE TABLE IF NOT EXISTS inbox_messages (\n    id UUID PRIMARY KEY,\n    type VARCHAR(255) NOT NULL,\n    content JSONB NOT NULL,\n    received_on_utc TIMESTAMP WITH TIME ZONE NOT NULL,\n    processed_on_utc TIMESTAMP WITH TIME ZONE NULL,\n    error TEXT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_inbox_messages_unprocessed\nON public.inbox_messages (received_on_utc, processed_on_utc)\nINCLUDE (id, type, content)\nWHERE processed_on_utc IS NULL;\n</code></pre>\n<p>The structure mirrors the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern's</strong></a> <code>outbox_messages</code> table.\nThe <code>id</code> enables idempotent inserts via <code>ON CONFLICT DO NOTHING</code>.\nThe filtered index keeps the index small since processed messages drop out automatically.</p>\n<p>Messages between services use a shared <code>IntegrationEvent</code> base record:</p>\n<pre><code class=\"language-csharp\">public abstract record IntegrationEvent(Guid MessageId);\n\npublic sealed record OrderCreatedIntegrationEvent(Guid OrderId)\n    : IntegrationEvent(Guid.CreateVersion7());\n</code></pre>\n<h2>Inbox Consumer</h2>\n<p>The consumer is a <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>MassTransit</strong></a> <code>IConsumer&lt;T&gt;</code>.\nInstead of processing the message, it <strong>writes it to the inbox table</strong> and returns.\nThat's it.</p>\n<p>We can make this generic so it works for any integration event:</p>\n<pre><code class=\"language-csharp\">internal sealed class InboxConsumer&lt;T&gt;(NpgsqlDataSource dataSource)\n    : IConsumer&lt;T&gt; where T : IntegrationEvent\n{\n    public async Task Consume(ConsumeContext&lt;T&gt; context)\n    {\n        await using var connection = await dataSource.OpenConnectionAsync(\n            context.CancellationToken);\n\n        const string sql =\n            @&quot;&quot;&quot;\n            INSERT INTO public.inbox_messages (id, type, content, received_on_utc)\n            VALUES (@Id, @Type, @Content::jsonb, @ReceivedOnUtc)\n            ON CONFLICT (id) DO NOTHING;\n            &quot;&quot;&quot;;\n\n        await connection.ExecuteAsync(sql, new\n        {\n            Id = context.Message.MessageId,\n            Type = typeof(T).FullName,\n            Content = JsonSerializer.Serialize(context.Message),\n            ReceivedOnUtc = DateTime.UtcNow\n        });\n    }\n}\n</code></pre>\n<p><code>ON CONFLICT (id) DO NOTHING</code> is doing the heavy lifting.\nIf the broker delivers the same message twice, the second insert is silently ignored.\nCrash after insert but before ACK? The next delivery is safely deduplicated.</p>\n<h2>Inbox Processor</h2>\n<p>The processor runs in a <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>background service</strong></a>\nor a <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>scheduled job</strong></a>,\nfetching unprocessed messages in batches and dispatching them for handling.</p>\n<pre><code class=\"language-csharp\">internal sealed class InboxProcessor(\n    NpgsqlDataSource dataSource,\n    IEventDispatcher eventDispatcher,\n    ILogger&lt;InboxProcessor&gt; logger)\n{\n    private const int BatchSize = 1000;\n\n    public async Task&lt;int&gt; Execute(CancellationToken cancellationToken = default)\n    {\n        await using var connection =\n            await dataSource.OpenConnectionAsync(cancellationToken);\n        await using var transaction =\n            await connection.BeginTransactionAsync(cancellationToken);\n\n        var messages = (await connection.QueryAsync&lt;InboxMessage&gt;(\n            @&quot;&quot;&quot;\n            SELECT id AS Id, type AS Type, content AS Content\n            FROM inbox_messages\n            WHERE processed_on_utc IS NULL\n            ORDER BY received_on_utc\n            LIMIT @BatchSize\n            FOR UPDATE SKIP LOCKED\n            &quot;&quot;&quot;,\n            new { BatchSize },\n            transaction: transaction)).AsList();\n\n        var processedAt = DateTime.UtcNow;\n        var results = new List&lt;(Guid Id, DateTime ProcessedAt, string? Error)&gt;(\n            messages.Count);\n\n        foreach (var message in messages)\n        {\n            try\n            {\n                var messageType = Type.GetType(message.Type)!;\n                var deserialized = JsonSerializer.Deserialize(\n                    message.Content, messageType)!;\n\n                await eventDispatcher.DispatchAsync(deserialized, cancellationToken);\n\n                results.Add((message.Id, processedAt, null));\n            }\n            catch (Exception ex)\n            {\n                logger.LogError(ex, &quot;Failed to process inbox message {Id}&quot;, message.Id);\n                results.Add((message.Id, processedAt, ex.ToString()));\n            }\n        }\n\n        if (results.Count &gt; 0)\n        {\n            await connection.ExecuteAsync(\n                @&quot;&quot;&quot;\n                UPDATE inbox_messages\n                SET processed_on_utc = v.processed_on_utc,\n                    error = v.error\n                FROM UNNEST(@Ids, @ProcessedAts, @Errors)\n                    AS v(id, processed_on_utc, error)\n                WHERE inbox_messages.id = v.id\n                &quot;&quot;&quot;,\n                new\n                {\n                    Ids = results.Select(r =&gt; r.Id).ToArray(),\n                    ProcessedAts = results.Select(r =&gt; r.ProcessedAt).ToArray(),\n                    Errors = results.Select(r =&gt; r.Error).ToArray()\n                },\n                transaction: transaction);\n        }\n\n        await transaction.CommitAsync(cancellationToken);\n\n        return messages.Count;\n    }\n}\n</code></pre>\n<ul>\n<li><strong><code>FOR UPDATE SKIP LOCKED</code></strong> lets multiple processor instances run concurrently\nwithout contention. I covered this in <a href=\"https://milanjovanovic.tech/blog/scaling-the-outbox-pattern\"><strong>scaling the Outbox pattern</strong></a>.</li>\n<li><strong>Batch update with <code>UNNEST</code></strong> writes all results in a single round-trip\nusing the same <a href=\"https://milanjovanovic.tech/blog/optimizing-bulk-database-updates-in-dotnet\"><strong>bulk update approach</strong></a>.</li>\n<li><strong>Error capture</strong>: failed messages get marked with the exception so they don't block the queue.</li>\n</ul>\n<p>If the process crashes mid-batch, the transaction rolls back\nand messages get picked up on the next run.</p>\n<h2>Things to Watch Out For</h2>\n<p><strong>Table growth.</strong>\nThe inbox table grows indefinitely.\nDelete processed messages after a retention period,\nor partition by time range and drop old partitions.\nYou can also archive them to another table if you need to keep a history.</p>\n<p><strong>Poison messages.</strong>\nIf a message consistently fails, it gets marked with an error each time.\nConsider a max retry count. After N failures, dead-letter it and alert.</p>\n<p><strong>Ordering.</strong>\n<code>ORDER BY received_on_utc</code> gives you rough arrival-time ordering.\nBut with <code>SKIP LOCKED</code> and multiple processors, strict ordering is <strong>not</strong> guaranteed.\nIf you need <a href=\"https://milanjovanovic.tech/blog/solving-message-ordering-from-first-principles\"><strong>per-aggregate ordering</strong></a>,\nyou'll need additional coordination.</p>\n<p><strong>Monitoring.</strong>\nTrack the lag between <code>received_on_utc</code> and <code>processed_on_utc</code>.\nIf this gap grows, increase the batch size, decrease the polling interval,\nor scale out more processor instances.</p>\n<h2>Inbox vs. Idempotent Consumer</h2>\n<p>Both the Inbox and the <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>Idempotent Consumer</strong></a> prevent duplicate processing.\nThe difference is <em>when</em> processing happens and <em>who controls</em> retries.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it\"><strong>Idempotent Consumer</strong></a> processes messages inline.\nIt checks a deduplication table, does the work, and records the dedup entry in the same transaction.\nIf processing fails, the transaction rolls back, no dedup record is written,\nand the <strong>broker</strong> redelivers the message on its own schedule.\nYou don't control retry timing or backoff.</p>\n<p>The Inbox separates reception from processing.\nThe consumer writes the message and ACKs immediately. The broker is done.\nIf the processor fails, it records the error and moves on.\nRetries are your responsibility: reset <code>processed_on_utc</code> to <code>NULL</code> for messages under a retry threshold,\nor run a separate loop that picks up failed messages after a delay.</p>\n<p>Use the <strong>Idempotent Consumer</strong> when your side effects are transactional\nand broker-managed retries are good enough.\nUse the <strong>Inbox</strong> when you need batching, custom retry policies,\nor horizontal scaling via <code>FOR UPDATE SKIP LOCKED</code>.</p>\n<h2>Summary</h2>\n<p>The Inbox pattern is the consumer-side counterpart to the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a>.</p>\n<ul>\n<li><strong><code>ON CONFLICT DO NOTHING</code></strong> makes consumer inserts idempotent</li>\n<li><strong>Separation of reception and processing</strong> gives you independent retry control</li>\n<li><strong><code>FOR UPDATE SKIP LOCKED</code></strong> enables horizontal scaling of the processor</li>\n<li><strong>Batch updates with <code>UNNEST</code></strong> minimize database round-trips</li>\n</ul>\n<p>If you want to see how I build <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>event-driven systems</strong></a> with these patterns,\ncheck out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-the-inbox-pattern-for-reliable-message-consumption",
            "title": "Implementing the Inbox Pattern for Reliable Message Consumption",
            "summary": "The Outbox pattern guarantees reliable publishing. But what about the consumer side? The Inbox pattern ensures each incoming message is processed exactly once…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_188.png",
            "date_modified": "2026-04-04T00:00:00.000Z",
            "date_published": "2026-04-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/getting-started-with-pgvector-in-dotnet-for-simple-vector-search",
            "content_html": "<p>Vector search in .NET does not require a dedicated vector database.\nThe pgvector extension gives PostgreSQL a native <code>vector</code> column, and you query it with the cosine distance operator <code>&lt;=&gt;</code>.\nAn HNSW index keeps those lookups fast once the table grows past a few hundred rows.</p>\n<p>Not every AI feature needs a dedicated vector database.</p>\n<p>Dedicated vector databases like <a href=\"https://www.pinecone.io/\">Pinecone</a>, <a href=\"https://qdrant.tech/\">Qdrant</a>, and <a href=\"https://weaviate.io/\">Weaviate</a> get all the attention.\nBut if your data already lives in PostgreSQL, you don't need another moving part.</p>\n<p><a href=\"https://github.com/pgvector/pgvector\">pgvector</a> is a PostgreSQL extension that adds vector storage and similarity search directly to your existing database.\nYou enable the extension, create a vector column, and start querying.</p>\n<p>In this week's issue, I'll walk you through:</p>\n<ul>\n<li>What <a href=\"https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide\"><strong>vector search</strong></a> is and when you need it</li>\n<li>Provisioning pgvector with .NET Aspire and Ollama</li>\n<li>Generating embeddings with <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><strong>MEAI</strong></a> and storing with Dapper</li>\n<li>Querying by semantic similarity using cosine distance</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>What Is Vector Search?</h2>\n<p>Traditional database queries work with exact matches.\nYou search for <code>&quot;authentication&quot;</code> and get rows containing that exact word.\nBut what about rows that mention <code>&quot;login&quot;</code>, <code>&quot;sign-in&quot;</code>, or <code>&quot;identity verification&quot;</code>?\nThose are semantically similar, but a <code>LIKE</code> query won't find them.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide\"><strong>Vector search</strong></a> solves this by comparing <strong>meaning</strong> instead of text.</p>\n<p>You convert text into an array of numbers (called an <strong>embedding</strong>) using a machine learning model.\nSemantically similar text produces similar arrays.\nThen, instead of matching keywords, you find the vectors that are <strong>closest</strong> to your query vector.</p>\n<p>When would you use this?</p>\n<ul>\n<li><strong>Semantic search</strong> - Find results by meaning, not just keywords</li>\n<li><a href=\"https://milanjovanovic.tech/blog/rag-system-dotnet\"><strong>RAG (Retrieval-Augmented Generation)</strong></a> - Feed relevant context to an LLM</li>\n<li><strong>Recommendations</strong> - &quot;Users who liked X also liked Y&quot;</li>\n<li><strong>Deduplication</strong> - Find near-duplicate content</li>\n</ul>\n<p>The key insight is that you don't need a specialized database for this.\nIf you're already on PostgreSQL, pgvector gives you all of this as an extension.</p>\n<h2>Provisioning Infrastructure With .NET Aspire</h2>\n<p>We'll use <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a>\nto provision a pgvector-enabled PostgreSQL container and an <a href=\"https://milanjovanovic.tech/blog/how-to-extract-structured-data-from-images-using-ollama-in-dotnet\"><strong>Ollama</strong></a>\ninstance with the <a href=\"https://ollama.com/library/qwen3-embedding\">qwen3-embedding</a> embedding model.</p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\nvar ollama = builder.AddOllama(&quot;ollama&quot;)\n    .WithLifetime(ContainerLifetime.Persistent)\n    .WithDataVolume()\n    .WithGPUSupport();\n\nvar embeddingModel = ollama.AddModel(&quot;qwen3-embedding:0.6b&quot;);\n\nvar postgres = builder.AddPostgres(&quot;postgres&quot;, port: 6432)\n    .WithLifetime(ContainerLifetime.Persistent)\n    .WithDataVolume()\n    .WithImage(&quot;pgvector/pgvector&quot;, &quot;pg17&quot;)\n    .AddDatabase(&quot;articles&quot;);\n\nbuilder.AddProject&lt;Projects.PgVector_Articles&gt;(&quot;pgvector-articles&quot;)\n    .WithReference(embeddingModel)\n    .WithReference(postgres)\n    .WaitFor(embeddingModel)\n    .WaitFor(postgres);\n\nbuilder.Build().Run();\n</code></pre>\n<p>The <code>pgvector/pgvector:pg17</code> image is PostgreSQL 17 with the pgvector extension pre-installed.\n<code>WithLifetime(ContainerLifetime.Persistent)</code> keeps the containers running across app restarts so you don't lose data during development.\n<code>WaitFor</code> ensures the database and model are ready before the API starts.</p>\n<p>If you're not using Aspire, you can run the same <code>pgvector/pgvector:pg17</code> image via <code>docker compose</code> and point to it with a regular connection string.</p>\n<h2>Configuring the API Project</h2>\n<p>The API project needs a few packages:</p>\n<pre><code class=\"language-bash\">dotnet add package Aspire.Npgsql\ndotnet add package Pgvector.Dapper\ndotnet add package CommunityToolkit.Aspire.OllamaSharp\n</code></pre>\n<p><code>Pgvector.Dapper</code> provides the <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a> type handler for the <code>Vector</code> type.\nOther than <a href=\"https://github.com/pgvector/pgvector-dotnet/tree/master/src/Pgvector.Dapper\">Pgvector.Dapper</a>, there are also libraries for <a href=\"https://github.com/pgvector/pgvector-dotnet/tree/master/src/Pgvector\">Npgsql</a> and <a href=\"https://github.com/pgvector/pgvector-dotnet/tree/master/src/Pgvector.EntityFrameworkCore\">EF Core</a> if you prefer those instead.</p>\n<p>Register the services in <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">builder.AddOllamaApiClient(&quot;ollama-qwen3-embedding&quot;)\n    .AddEmbeddingGenerator();\n\nbuilder.AddNpgsqlDataSource(&quot;articles&quot;, configureDataSourceBuilder: b =&gt;\n{\n    b.UseVector();\n});\n\nSqlMapper.AddTypeHandler(new VectorTypeHandler());\n</code></pre>\n<p><code>AddEmbeddingGenerator()</code> registers an <code>IEmbeddingGenerator&lt;string, Embedding&lt;float&gt;&gt;</code> using the <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><code>Microsoft.Extensions.AI</code></a> abstraction.\n<code>UseVector()</code> enables pgvector type mapping on the Npgsql data source.\nThe <code>VectorTypeHandler</code> lets Dapper serialize and deserialize <code>Vector</code> parameters.</p>\n<h2>Initializing the Database</h2>\n<p>Before storing vectors, we need to enable the pgvector extension and create a table.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/init&quot;, async (NpgsqlDataSource dataSource) =&gt;\n{\n    await using var conn = await dataSource.OpenConnectionAsync();\n\n    await using var enableExt = new NpgsqlCommand(\n        &quot;CREATE EXTENSION IF NOT EXISTS vector&quot;, conn);\n    await enableExt.ExecuteNonQueryAsync();\n\n    conn.ReloadTypes();\n\n    await conn.ExecuteAsync(\n        &quot;&quot;&quot;\n        CREATE TABLE IF NOT EXISTS articles (\n            id SERIAL PRIMARY KEY,\n            url TEXT NOT NULL,\n            title TEXT NOT NULL,\n            embedding vector(1024) NOT NULL\n        )\n        &quot;&quot;&quot;);\n\n    await conn.ExecuteAsync(\n        &quot;&quot;&quot;\n        CREATE INDEX IF NOT EXISTS articles_embedding_idx\n        ON articles USING hnsw (embedding vector_cosine_ops)\n        &quot;&quot;&quot;);\n\n    return Results.Ok(&quot;Database initialized.&quot;);\n});\n</code></pre>\n<p>A few things to note:</p>\n<ul>\n<li><code>CREATE EXTENSION IF NOT EXISTS vector</code> enables pgvector in the database</li>\n<li><code>embedding vector(1024)</code> defines a vector column with 1024 dimensions, matching the Ollama embedding model's output (<code>qwen3-embedding:0.6b</code>)</li>\n<li><code>conn.ReloadTypes()</code> refreshes Npgsql's type cache so it recognizes the new <code>vector</code> type</li>\n<li>The <strong>HNSW index</strong> with <code>vector_cosine_ops</code> enables fast approximate nearest-neighbor search using cosine distance.</li>\n</ul>\n<p><a href=\"https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world\">HNSW</a> (Hierarchical Navigable Small World) is a graph-based algorithm that builds a multi-layer structure for efficient similarity lookups.</p>\n<p>Without the index, pgvector does a sequential scan over every row.\nThat's fine for hundreds of rows, but HNSW keeps queries fast as the dataset grows.</p>\n<h2>Generating and Storing Embeddings</h2>\n<p>Now we generate embeddings for our content and store them alongside the data.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/embeddings/generate&quot;, async (\n    BlogService blogService,\n    IEmbeddingGenerator&lt;string, Embedding&lt;float&gt;&gt; embeddingGenerator,\n    NpgsqlDataSource dataSource,\n    ILogger&lt;Program&gt; logger) =&gt;\n{\n    await using var conn = await dataSource.OpenConnectionAsync();\n    conn.ReloadTypes();\n\n    int count = 0;\n\n    foreach (var articleUrl in File.ReadAllLines(&quot;sitemap_urls.txt&quot;))\n    {\n        var (title, content) = await blogService.GetTitleAndContentAsync(articleUrl);\n\n        var embedding = await embeddingGenerator.GenerateAsync(content);\n\n        await conn.ExecuteAsync(\n            &quot;INSERT INTO articles (url, title, embedding) VALUES (@url, @title, @embedding)&quot;,\n            new\n            {\n                url = articleUrl,\n                title,\n                embedding = new Vector(embedding.Vector.ToArray())\n            });\n\n        count++;\n        logger.LogInformation(&quot;Processed ({Count}): {Url}&quot;, count, articleUrl);\n    }\n\n    return Results.Ok(new { processed = count });\n});\n</code></pre>\n<p><code>embeddingGenerator.GenerateAsync(content)</code> sends the text to the Ollama model and returns a vector.\nWe wrap it in a <code>Pgvector.Vector</code> and Dapper handles the rest.</p>\n<p>The <code>IEmbeddingGenerator</code> is provider-agnostic.\nIf you want to swap Ollama for OpenAI or Azure OpenAI later, only the registration in <code>Program.cs</code> changes.\nYour endpoint code stays the same.</p>\n<h2>Similarity Search With Cosine Distance</h2>\n<p>This is where it gets interesting.\nTo search, we embed the query text and find the closest vectors in the database.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/search&quot;, async (\n    string query,\n    IEmbeddingGenerator&lt;string, Embedding&lt;float&gt;&gt; embeddingGenerator,\n    NpgsqlDataSource dataSource,\n    int limit = 5) =&gt;\n{\n    var searchEmbedding = await embeddingGenerator.GenerateAsync(query);\n\n    await using var con = await dataSource.OpenConnectionAsync();\n    con.ReloadTypes();\n\n    var embedding = new Vector(searchEmbedding.Vector.ToArray());\n\n    var results = await con.QueryAsync&lt;SearchResult&gt;(\n        @&quot;&quot;&quot;\n        SELECT title, url, embedding &lt;=&gt; @embedding as distance\n        FROM articles\n        ORDER BY embedding &lt;=&gt; @embedding\n        LIMIT @limit\n        &quot;&quot;&quot;,\n        new { embedding, limit });\n\n    return Results.Ok(new { query, results });\n});\n\nrecord SearchResult(string Title, string Url, double Distance);\n</code></pre>\n<p>The <code>&lt;=&gt;</code> operator is pgvector's <strong>cosine distance</strong> function, where lower values mean more similar.\nWe order by distance ascending and take the top N matches.</p>\n<p>The critical part: the query text must be embedded with the <strong>same model</strong> that produced the stored embeddings.\nDifferent models produce vectors in different embedding spaces, and comparing them would be meaningless.</p>\n<p>There are also shared embedding spaces models that can embed text and images into compatible vectors, but that's a more advanced topic.\nOne example is the <a href=\"https://blog.voyageai.com/2026/01/15/voyage-4/\">Voyage 4 model family</a>.</p>\n<p>A query like <code>&quot;how to secure an API&quot;</code> will surface articles about authentication, JWT validation, and authorization, even if they don't contain those exact words.</p>\n<p>pgvector supports three distance operators:</p>\n<ul>\n<li><code>&lt;-&gt;</code> - L2 (Euclidean) distance, uses <code>vector_l2_ops</code></li>\n<li><code>&lt;=&gt;</code> - Cosine distance, uses <code>vector_cosine_ops</code></li>\n<li><code>&lt;#&gt;</code> - Inner product (negative), uses <code>vector_ip_ops</code></li>\n</ul>\n<p>Cosine distance is the most common choice for text embeddings.</p>\n<h2>Summary</h2>\n<p>You don't need a dedicated vector database to add semantic search to your application.\nIf you're already running PostgreSQL, pgvector gives you vector storage and similarity search without adding new infrastructure.</p>\n<p>Here's what we covered:</p>\n<ul>\n<li><strong>pgvector</strong> is a PostgreSQL extension - enable it and you get a native <code>vector</code> column type</li>\n<li><strong>.NET Aspire</strong> provisions pgvector-enabled PostgreSQL and Ollama with minimal configuration</li>\n<li><strong>Embeddings</strong> turn text into vectors using models like <code>qwen3-embedding</code> via <code>IEmbeddingGenerator</code></li>\n<li><strong>Similarity search</strong> uses the cosine distance operator (<code>&lt;=&gt;</code>) to find the closest matches</li>\n<li><strong>HNSW indexes</strong> make vector queries fast at scale</li>\n</ul>\n<p>Your vectors live right next to your relational data, so you can join, filter, and paginate just like any other query without syncing between databases or managing extra infrastructure.</p>\n<p>If you want to explore more advanced scenarios like <a href=\"https://milanjovanovic.tech/blog/building-semantic-search-with-amazon-s3-vectors-and-semantic-kernel\"><strong>building semantic search with S3 Vectors and Semantic Kernel</strong></a>, I've covered that in a previous article.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/getting-started-with-pgvector-in-dotnet-for-simple-vector-search",
            "title": "Getting Started With PgVector in .NET for Simple Vector Search",
            "summary": "Vector search doesn't require a dedicated vector database. PostgreSQL with pgvector gives you similarity search right next to your relational data.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_187.png",
            "date_modified": "2026-03-28T00:00:00.000Z",
            "date_published": "2026-03-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/scaling-signalr-with-redis-backplane",
            "content_html": "<p>Each SignalR server only knows about the connections it accepted, so once you scale out, messages for clients on another instance are silently dropped.\nA Redis backplane fixes that by publishing every message to a shared channel that all instances subscribe to.\nYou still need sticky sessions, and nothing is buffered while Redis is down.</p>\n<p>I ran into this one the hard way.</p>\n<p>I built a <a href=\"https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr\"><strong>real-time notification feature with SignalR</strong></a>, tested it locally, everything worked great.\nThen I scaled to two instances behind a load balancer, and notifications started disappearing for some users.</p>\n<p>The code was fine.\nThe problem was that <strong>SignalR connections are bound to the server process that accepted them</strong>.\nEach instance only knows about its own connections.\nSo when an API request lands on Server 1 but the user is connected to Server 2, the notification just... doesn't get delivered.</p>\n<p>This is the SignalR scale-out problem, and it bites almost everyone who goes from one instance to more.</p>\n<h2>Why SignalR Breaks When You Scale Out</h2>\n<p>With a single instance, everything just works.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_186/signalr_single_server.png\" alt=\"SignalR single server deployment showing all clients connected to the same server.\">\n<p>The server holds the full map of who's connected, so sending a message to a user, a group, or all clients works because that map is complete.</p>\n<p>But scale out to two or more instances, and that map fractures.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_186/signalr_multi_server.png\" alt=\"SignalR multi-server deployment showing clients connected to different servers.\">\n<p>Server 1 has no idea Client 3 or Client 4 even exist.\nSo when an order status change happens on Server 1 and needs to reach Client 3, Server 1 checks its connection map, finds nothing, and the message is quietly dropped.</p>\n<h2>The Backplane Pattern</h2>\n<p>The fix is a <strong>backplane</strong> - a shared messaging layer that sits between all your server instances.</p>\n<p>Every server publishes outgoing messages to a central channel, and every server subscribes to that same channel.\nWhen a message comes in, each server checks if any of its local connections should receive it.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_186/signalr_backplane.png\" alt=\"SignalR backplane deployment showing clients connected to different servers.\">\n<p>When Server 1 wants to notify Client 3:</p>\n<ol>\n<li>Server 1 publishes the message to the backplane</li>\n<li>All servers receive the message</li>\n<li>Server 2 recognizes Client 3 as one of its connections and delivers the notification</li>\n</ol>\n<p>From your code's perspective, it looks like every server can see every connection.\nRedis works really well for this because its <a href=\"https://milanjovanovic.tech/blog/simple-messaging-in-dotnet-with-redis-pubsub\"><strong>Pub/Sub</strong></a> delivers messages to all subscribers in near real-time.\nAnd if you're already using Redis for <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>distributed caching</strong></a>, you don't even need to spin up anything new.</p>\n<h2>Setting It Up</h2>\n<p>Let me walk through how I set this up in an order notification system.\nClients connect to a SignalR hub, authenticate via <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT</strong></a>, and get real-time status updates when an order changes.</p>\n<h3>Install the NuGet Package</h3>\n<pre><code class=\"language-bash\">dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis\n</code></pre>\n<h3>Register the Backplane</h3>\n<p>Chain <code>.AddStackExchangeRedis()</code> onto your <code>AddSignalR()</code> call:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSignalR()\n    .AddStackExchangeRedis(builder.Configuration.GetConnectionString(&quot;cache&quot;)!);\n</code></pre>\n<p>If you're running with <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a>,\nyou will have the Redis connection string registered via environment variables, so you can just pull it from configuration.\nReference the same named connection:</p>\n<pre><code class=\"language-csharp\">builder.AddRedisDistributedCache(&quot;cache&quot;);\n\nbuilder.Services.AddSignalR()\n    .AddStackExchangeRedis(builder.Configuration.GetConnectionString(&quot;cache&quot;)!);\n</code></pre>\n<p>If you have multiple SignalR apps sharing the same Redis instance, you'll want to add a channel prefix.\nOtherwise, messages from one app will reach subscribers in every app on that Redis server.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSignalR()\n    .AddStackExchangeRedis(connectionString, options =&gt;\n    {\n        options.Configuration.ChannelPrefix = RedisChannel.Literal(&quot;OrderNotifications&quot;);\n    });\n</code></pre>\n<p>The nice thing is that your <code>IHubContext&lt;&gt;</code> call site doesn't change at all.\n<code>Clients.User(...)</code> works the same whether you have one instance or ten - the backplane handles routing behind the scenes.</p>\n<p>When I tested this with two replicas in <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a>, I tagged each notification with the sending instance's ID.\nA client on Replica 1 received a notification stamped with Replica 2's ID, which confirmed the message was crossing instances through Redis.</p>\n<h2>The Sticky Sessions Requirement</h2>\n<p>This is something you should know before you set up the backplane: <strong>you still need sticky sessions</strong>.</p>\n<p>The Redis backplane solves message <em>routing</em>, but it does <strong>not</strong> remove the need for sticky sessions.</p>\n<p>SignalR's connection negotiation is a two-step process:</p>\n<ol>\n<li>The client sends a <code>POST</code> to <code>/hub/negotiate</code> to obtain a connection token</li>\n<li>The client uses that token to establish the WebSocket connection</li>\n</ol>\n<p>Both requests must land on the <strong>same server</strong>.\nIf your load balancer routes the negotiation to Server 1 but the WebSocket upgrade to Server 2, the connection fails.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_186/sticky_sessions.png\" alt=\"SignalR connection negotiation showing the need for sticky sessions.\">\n<p><strong>Make sure sticky sessions are enabled in your load balancer.</strong>\nMost load balancers support this via IP hash or cookie affinity - check the docs for whichever you're using.</p>\n<h2>What Happens When Redis Goes Down?</h2>\n<p>One thing worth knowing: <strong>SignalR does not buffer messages when Redis is unavailable</strong>.</p>\n<p>If Redis stops responding, any messages sent during the outage are simply lost.\nSignalR may throw exceptions, but your existing WebSocket connections stay open - clients don't get disconnected.\nOnce Redis comes back, SignalR reconnects automatically.</p>\n<p>For most real-time scenarios like order updates or live dashboards, this is fine.\nThe next state change triggers a fresh notification anyway, or the user can just reload.\nIf you're dealing with something more critical (financial data, operational alerts), you'll want a reconciliation strategy on reconnect or a durable queue running alongside.</p>\n<h2>Redis Backplane vs. Azure SignalR Service</h2>\n<p>If you're on Azure, the managed <a href=\"https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-overview\">Azure SignalR Service</a> is worth considering.\nIt proxies all client connections through the service, so sticky sessions aren't required and your servers only hold a small number of constant connections to the service.</p>\n<p>The Redis backplane is the better fit when you're self-hosted, latency-sensitive, or already running Redis.\nFor everything else, Azure SignalR Service is the cleaner option.</p>\n<h2>Summary</h2>\n<p>Honestly, the Redis backplane is almost <em>too</em> simple to set up.\nOne method call on <code>AddSignalR()</code> and your app goes from silently dropping messages to routing them across every instance.\nYou don't need to make changes to your hub code, client code, or application logic.</p>\n<p>Just remember two things:</p>\n<ul>\n<li><strong>You still need sticky sessions</strong></li>\n<li><strong>Messages aren't buffered</strong> if Redis goes down temporarily</li>\n</ul>\n<p>Get those two right and SignalR scales out just as smoothly as the rest of your stack.</p>\n<p>If you want to go deeper on building real-time features and APIs in .NET,\ncheck out my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a> course.</p>\n<p>Hope this was useful. See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/scaling-signalr-with-redis-backplane",
            "title": "Scaling SignalR With a Redis Backplane",
            "summary": "SignalR connections are server-local. Scale out to multiple instances and messages stop reaching the right clients.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_186.png",
            "date_modified": "2026-03-21T00:00:00.000Z",
            "date_published": "2026-03-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/optimizing-bulk-database-updates-in-dotnet",
            "content_html": "<p>What makes a bulk update slow is the number of round-trips to the database.\nUpdating 10,000 rows with one <code>UPDATE</code> per row took 2,414ms in my benchmark, while <code>UNNEST</code> with array parameters and a temp table loaded by binary <code>COPY</code> both finished in 41ms.\nThose two keep the query text constant no matter how large the batch gets.</p>\n<p>Every outbox processor, job queue, and batch pipeline hits the same problem at some point:\nmark a set of rows as done in bulk.</p>\n<p>I went through this recently while optimizing an outbox processor.\nI measured seven different approaches against 1,000, 10,000, and 25,000 rows in PostgreSQL.\nAt 10,000 rows the slowest approach took 2,414ms and the fastest took 41ms.\nThe bottleneck was almost never the SQL.\nIt was how many times the code was talking to the database.</p>\n<p>The scenario: mark a batch of orders as processed by setting a status and a <strong>unique</strong> <code>processed_at</code> timestamp per row.\nThat uniqueness constraint is what makes it tricky.\nYou can't use a simple <code>UPDATE ... WHERE id IN (...)</code> with a single value because every row gets a different timestamp.</p>\n<p>Let's go through each approach.</p>\n<h2>The Scenario</h2>\n<p>A table of orders, each needing two updates: a <code>status</code> change to <code>&quot;Processed&quot;</code> and a unique <code>processed_at</code> timestamp.</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n    id            UUID         NOT NULL PRIMARY KEY,\n    customer_name TEXT         NOT NULL,\n    status        TEXT         NOT NULL DEFAULT 'Pending',\n    processed_at  TIMESTAMPTZ\n);\n</code></pre>\n<p>The update payload is a simple record pairing each order with its timestamp:</p>\n<pre><code class=\"language-csharp\">record OrderUpdate(Guid Id, DateTime ProcessedAt);\n</code></pre>\n<p>With 10,000 of these, here's how each approach plays out.</p>\n<h2>Approach 1: Naive Dapper, One UPDATE Per Row</h2>\n<p>The first thing you'd try: loop through the list and fire one <code>UPDATE</code> per row.</p>\n<pre><code class=\"language-csharp\">await using var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\nawait using var transaction = await connection.BeginTransactionAsync();\n\nforeach (var update in updates)\n{\n    await connection.ExecuteAsync(\n        &quot;&quot;&quot;\n        UPDATE orders\n        SET processed_at = @ProcessedAt,\n            status       = 'Processed'\n        WHERE id = @Id\n        &quot;&quot;&quot;,\n        new { update.Id, update.ProcessedAt },\n        transaction: transaction);\n}\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>10,000 rows means 10,000 round-trips to the database.\nEach <code>ExecuteAsync</code> call sends the SQL, waits for PostgreSQL to respond, then moves on to the next one.\nAt 10,000 rows this took <strong>2,414ms</strong> in my benchmark. At 25,000 rows it was over 6 seconds.\nOver a real network it would be worse.\nThe database is not slow. The constant back-and-forth is.</p>\n<h2>Approach 2: EF Core SaveChanges, Batched Round-Trips</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>EF Core</strong></a> batches the generated SQL statements to cut down on round-trips.\nWith <code>MaxBatchSize</code> set high enough, you can push all 10,000 updates in far fewer network calls.\nThe default batch size is 42, in case you were wondering.\nYou can read more about this in the <a href=\"https://learn.microsoft.com/en-us/ef/core/performance/efficient-updating\">EF Core efficient updating docs</a>.</p>\n<pre><code class=\"language-csharp\">var options = new DbContextOptionsBuilder&lt;AppDbContext&gt;()\n    .UseNpgsql(connectionString, o =&gt; o.MinBatchSize(5000).MaxBatchSize(10000))\n    .Options;\n\nawait using var db = new AppDbContext(options);\n\nvar ids = updates.Select(u =&gt; u.Id).ToHashSet();\nvar orders = await db.Orders\n    .Where(o =&gt; ids.Contains(o.Id))\n    .ToListAsync();\n\nvar updateMap = updates.ToDictionary(u =&gt; u.Id);\n\nforeach (var order in orders)\n{\n    order.Status = &quot;Processed&quot;;\n    order.ProcessedAt = updateMap[order.Id].ProcessedAt;\n}\n\nawait db.SaveChangesAsync();\n</code></pre>\n<p>It's a meaningful improvement over the loop.\nAt 10,000 rows it came in at <strong>1,030ms</strong>, roughly half the time of Approach 1.\nBut there's hidden cost in two places: the upfront <code>SELECT</code> to load all 10,000 entities into the change tracker,\nand the fact that EF Core still generates 10,000 individual <code>UPDATE</code> statements.\nThey're packed into fewer round-trips, but the SQL is all still there.</p>\n<h2>Approach 3: Dapper with a VALUES Table, One Statement, One Round-Trip</h2>\n<p>Instead of sending N separate statements, you can send a single <code>UPDATE</code> that supplies all the new values inline\nusing a derived <code>VALUES</code> table:</p>\n<pre><code class=\"language-sql\">UPDATE orders\nSET processed_at = v.processed_at,\n    status       = 'Processed'\nFROM (VALUES\n    (@Id0, @ProcessedAt0),\n    (@Id1, @ProcessedAt1),\n    ...\n) AS v(id, processed_at)\nWHERE orders.id = v.id::uuid\n</code></pre>\n<p>Here's the C# code to build and execute that:</p>\n<pre><code class=\"language-csharp\">await using var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\nawait using var transaction = await connection.BeginTransactionAsync();\n\nconst string updateTemplate =\n    @&quot;&quot;&quot;\n    UPDATE orders\n    SET processed_at = v.processed_at,\n        status       = 'Processed'\n    FROM (VALUES\n        {0}\n    ) AS v(id, processed_at)\n    WHERE orders.id = v.id::uuid\n    &quot;&quot;&quot;;\n\nvar paramNames = string.Join(\n    &quot;,\\n    &quot;,\n    updates.Select((_, i) =&gt; $&quot;(@Id{i}, @ProcessedAt{i})&quot;));\n\nvar sql = string.Format(updateTemplate, paramNames);\n\nvar parameters = new DynamicParameters();\nfor (int i = 0; i &lt; updates.Count; i++)\n{\n    parameters.Add($&quot;Id{i}&quot;, updates[i].Id.ToString());\n    parameters.Add($&quot;ProcessedAt{i}&quot;, updates[i].ProcessedAt);\n}\n\nawait connection.ExecuteAsync(sql, parameters, transaction: transaction);\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>PostgreSQL receives one statement, builds one execution plan, and updates all rows in a single pass.\nOne round-trip total.\nThis dropped from 2,414ms down to <strong>89ms</strong> at 10,000 rows.</p>\n<p>There is a trade-off: the SQL string grows with the batch size.\nAt 10,000 rows you end up with 10,000 parameter pairs in the query text.\nPostgreSQL allows a <strong>maximum of 65,535 parameters</strong>, so you have headroom,\nbut it is something to be aware of at very large batch sizes.\nApproaches 6 and 7 avoid this entirely.</p>\n<h2>Approach 4: EF Core ExecuteSqlRaw, Same SQL Inside EF Core</h2>\n<p>The SQL here is exactly the same as Approach 3.\nThe reason to use this instead is if you're already working inside an EF Core <code>DbContext</code> and want your bulk update to share the same transaction as other EF Core operations.</p>\n<pre><code class=\"language-csharp\">await using var db = new AppDbContext(options);\nawait using var transaction = await db.Database.BeginTransactionAsync();\n\nvar paramEntries = new List&lt;NpgsqlParameter&gt;();\nvar valueClauses = new List&lt;string&gt;();\n\nfor (int i = 0; i &lt; updates.Count; i++)\n{\n    valueClauses.Add($&quot;(@Id{i}::uuid, @ProcessedAt{i})&quot;);\n    paramEntries.Add(new NpgsqlParameter($&quot;Id{i}&quot;, updates[i].Id.ToString()));\n    paramEntries.Add(new NpgsqlParameter($&quot;ProcessedAt{i}&quot;, updates[i].ProcessedAt));\n}\n\nvar sql = string.Format(updateTemplate, string.Join(&quot;,\\n    &quot;, valueClauses));\n\nawait db.Database.ExecuteSqlRawAsync(sql, paramEntries);\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>The performance is the same as Approach 3 since it's the same SQL hitting the database.\nIn my benchmark it came in at <strong>166ms</strong> at 10,000 rows, a bit slower than Approach 3's 89ms.\nThe small gap is likely overhead from the EF Core transaction wrapper rather than the SQL itself.\nWhat you get is the ability to mix change-tracked operations with raw SQL inside the same EF Core transaction.\n<a href=\"https://milanjovanovic.tech/blog/ef-core-vs-dapper\"><strong>Dapper and EF Core</strong></a> are not mutually exclusive.</p>\n<p>One thing to watch out for: <code>ExecuteSqlRawAsync</code> doesn't accept Dapper's <code>DynamicParameters</code>.\nYou have to use <a href=\"https://www.npgsql.org/doc/api/Npgsql.NpgsqlParameter.html\"><code>NpgsqlParameter</code></a> objects directly.\nIf you want a full overview of EF Core's raw SQL options,\nI covered them in <a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries\"><strong>this article</strong></a>.</p>\n<h2>Approach 5: Dapper CTE (WITH ... AS VALUES)</h2>\n<p>This is a variation on Approach 3 that wraps the same <code>VALUES</code> data in a named CTE instead of an inline derived table:</p>\n<pre><code class=\"language-sql\">WITH updates(id, processed_at) AS (\n    VALUES\n        (@Id0::uuid, @ProcessedAt0),\n        (@Id1::uuid, @ProcessedAt1),\n        ...\n)\nUPDATE orders\nSET processed_at = updates.processed_at,\n    status       = 'Processed'\nFROM updates\nWHERE orders.id = updates.id\n</code></pre>\n<p>And here's the C# code to execute it:</p>\n<pre><code class=\"language-csharp\">await using var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\nawait using var transaction = await connection.BeginTransactionAsync();\n\nvar valueClauses = string.Join(\n    &quot;,\\n        &quot;,\n    updates.Select((_, i) =&gt; $&quot;(@Id{i}::uuid, @ProcessedAt{i})&quot;));\n\nvar sql =\n    @$&quot;&quot;&quot;\n    WITH updates(id, processed_at) AS (\n        VALUES\n            {valueClauses}\n    )\n    UPDATE orders\n    SET processed_at = updates.processed_at,\n        status       = 'Processed'\n    FROM updates\n    WHERE orders.id = updates.id\n    &quot;&quot;&quot;;\n\nvar parameters = new DynamicParameters();\nfor (int i = 0; i &lt; updates.Count; i++)\n{\n    parameters.Add($&quot;Id{i}&quot;, updates[i].Id.ToString());\n    parameters.Add($&quot;ProcessedAt{i}&quot;, updates[i].ProcessedAt);\n}\n\nawait connection.ExecuteAsync(sql, parameters, transaction: transaction);\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>Still a single statement and a single round-trip.\nPostgreSQL materializes the CTE once and joins against it for the update.\nIn the benchmark it came in at <strong>103ms</strong> at 10,000 rows, slightly behind the plain <code>VALUES</code> approach at 89ms.\nThe performance difference is small enough that this is really a style choice.\nSome teams prefer the CTE form when the update logic is more involved and they want to name the data source explicitly.</p>\n<h2>Approach 6: Dapper with UNNEST (PostgreSQL)</h2>\n<p>If you're on PostgreSQL, there's a cleaner option: <a href=\"https://www.postgresql.org/docs/current/functions-array.html\"><code>UNNEST</code></a>.\nInstead of building <code>@Id0</code> through <code>@Id9999</code> dynamically, you pass two arrays as parameters and let PostgreSQL expand them:</p>\n<pre><code class=\"language-csharp\">await using var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\nawait using var transaction = await connection.BeginTransactionAsync();\n\nvar ids = updates.Select(u =&gt; u.Id).ToArray();\nvar processedAts = updates.Select(u =&gt; u.ProcessedAt).ToArray();\n\nawait connection.ExecuteAsync(\n    @&quot;&quot;&quot;\n    UPDATE orders\n    SET processed_at = v.processed_at,\n        status       = 'Processed'\n    FROM UNNEST(@Ids, @ProcessedAts) AS v(id, processed_at)\n    WHERE orders.id = v.id\n    &quot;&quot;&quot;,\n    new { Ids = ids, ProcessedAts = processedAts },\n    transaction: transaction);\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>This is my preferred approach when working with PostgreSQL:</p>\n<ul>\n<li><strong>No dynamic SQL.</strong> The query text is always the same. No <code>string.Format</code>, no growing parameter lists.</li>\n<li><strong>Two parameters total.</strong> <code>@Ids</code> and <code>@ProcessedAts</code>.\nNpgsql maps <code>Guid[]</code> and <code>DateTime[]</code> directly to PostgreSQL array types.</li>\n<li><strong>Stable query plans.</strong> The SQL never changes,\nso PostgreSQL can cache and reuse the execution plan regardless of how many rows you're updating.</li>\n<li><strong>Scales cleanly.</strong> The query text stays constant for any batch size.\nOnly the array data grows, and it's sent as compact binary.</li>\n</ul>\n<p>The catch: <code>UNNEST</code> is PostgreSQL-specific.\nIf you're targeting multiple databases, stick with the <code>VALUES</code> approach from Approach 3, 4, or 5.\nOr you can research the equivalent array expansion functions in your other database of choice.</p>\n<h2>Approach 7: Temp Table + Binary COPY</h2>\n<p>All the previous approaches pass data as SQL parameters.\nAt extreme batch sizes that starts to become a constraint, both from the 65,535 parameter limit and from the overhead of building large parameter lists.</p>\n<p>A completely different path: skip parameters altogether.\nCreate a temporary staging table, bulk-load the data using <a href=\"https://www.npgsql.org/doc/copy.html\">Npgsql's binary COPY</a>, then run a single <code>UPDATE ... FROM</code>.</p>\n<pre><code class=\"language-csharp\">await using var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\nawait using var transaction = await connection.BeginTransactionAsync();\n\nawait connection.ExecuteAsync(\n    @&quot;&quot;&quot;\n    CREATE TEMP TABLE temp_updates (\n        id           UUID        NOT NULL,\n        processed_at TIMESTAMPTZ NOT NULL\n    ) ON COMMIT DROP\n    &quot;&quot;&quot;,\n    transaction: transaction);\n\nawait using (var writer = await connection.BeginBinaryImportAsync(\n    &quot;COPY temp_updates (id, processed_at) FROM STDIN (FORMAT BINARY)&quot;))\n{\n    foreach (var u in updates)\n    {\n        await writer.StartRowAsync();\n        await writer.WriteAsync(u.Id, NpgsqlTypes.NpgsqlDbType.Uuid);\n        await writer.WriteAsync(u.ProcessedAt, NpgsqlTypes.NpgsqlDbType.TimestampTz);\n    }\n    await writer.CompleteAsync();\n}\n\nawait connection.ExecuteAsync(\n    @&quot;&quot;&quot;\n    UPDATE orders\n    SET processed_at = t.processed_at,\n        status       = 'Processed'\n    FROM temp_updates t\n    WHERE orders.id = t.id\n    &quot;&quot;&quot;,\n    transaction: transaction);\n\nawait transaction.CommitAsync();\n</code></pre>\n<p>Binary COPY is Npgsql's most efficient data loading path.\nIt bypasses the SQL parameter system and streams rows directly in PostgreSQL's binary wire format.\nThe <code>ON COMMIT DROP</code> means the temp table is cleaned up automatically when the transaction ends.</p>\n<p>The trade-off is complexity: you create a table, stream data into it, then fire the update.\nIt is technically two operations (COPY + UPDATE), but both are efficient.\nAt 10,000 rows it came in at <strong>41ms</strong>, matching UNNEST.\nAt larger batch sizes (say, 100k+ rows), binary COPY would likely pull ahead further since the data payload grows but the query text stays constant and there are no parameter limits to worry about.</p>\n<h2>Benchmark Results</h2>\n<p>Here are the numbers across all three batch sizes:</p>\n<pre><code>| Approach                                   | 1,000 rows | 10,000 rows | 25,000 rows |\n| ------------------------------------------ | ---------- | ----------- | ----------- |\n| Approach 1: Naive Dapper                   | 317ms      | 2,414ms     | 6,283ms     |\n| Approach 2: EF Core SaveChanges            | 575ms      | 1,030ms     | 1,767ms     |\n| Approach 3: Dapper + VALUES table          | 19ms       | 89ms        | 233ms       |\n| Approach 4: EF Core ExecuteSqlRaw + VALUES | 58ms       | 166ms       | 282ms       |\n| Approach 5: Dapper + CTE                   | 13ms       | 103ms       | 251ms       |\n| Approach 6: Dapper + UNNEST                | 12ms       | 41ms        | 92ms        |\n| Approach 7: Temp table + binary COPY       | 11ms       | 41ms        | 93ms        |\n</code></pre>\n<p>A few things stand out.\nEF Core <code>SaveChanges</code> is actually faster than naive Dapper at 10,000+ rows because its batching cuts down round-trips significantly.\nBut both are far behind the single-statement approaches.\nThe biggest jump in the table is from Approach 2 to Approach 3.\nApproach 5 (CTE) is essentially the same performance as Approach 3 (VALUES), confirming it is a style choice rather than a performance one.\nApproaches 6 and 7 are the fastest at every scale, and the gap over CTE and VALUES widens as batch size grows.</p>\n<h2>Summary</h2>\n<p>The takeaway from this exercise is simple: round-trips are expensive and individual SQL statements add up fast.</p>\n<p>Reducing 10,000 database calls to 1 is where all the gains come from.\nEverything else is secondary.</p>\n<p>A few things worth keeping in mind:</p>\n<ul>\n<li><strong>EF Core <code>SaveChanges</code> is not a bulk update tool.</strong>\nIt reduces round-trips through batching, but it still generates N individual <code>UPDATE</code> statements\nand requires a <code>SELECT</code> to load the change tracker.\nFor bulk mutations, raw SQL is a better fit.\nThe same logic applies to inserts - I covered that in\n<a href=\"https://milanjovanovic.tech/blog/fast-sql-bulk-inserts-with-csharp-and-ef-core\"><strong>Fast SQL Bulk Inserts with C# and EF Core</strong></a>.</li>\n<li><strong>For PostgreSQL, <code>UNNEST</code> and binary COPY are the best options at scale.</strong>\nBoth use a fixed query text that doesn't grow with batch size and have no parameter count limits.\n<code>VALUES</code> and CTE are solid choices for smaller batches or when you need to stay closer to portable SQL.</li>\n<li><strong>Dapper and EF Core work well together.</strong>\nYou don't have to choose one or the other.\nQuery with EF Core, bulk-update with raw SQL, share the same transaction.</li>\n</ul>\n<p>If you want to go deeper on SQL performance more broadly,\nI recently wrote about <a href=\"https://milanjovanovic.tech/blog/debunking-the-filter-early-join-later-sql-performance-myth\"><strong>a common myth around filter and join ordering in SQL queries</strong></a>\nthat trips up a lot of developers.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/optimizing-bulk-database-updates-in-dotnet",
            "title": "Optimizing Bulk Database Updates in .NET: From Naive to Lightning-Fast",
            "summary": "Seven approaches to bulk-updating rows in PostgreSQL from .NET using Dapper and EF Core, from naive per-row updates to binary COPY.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_185.png",
            "date_modified": "2026-03-14T00:00:00.000Z",
            "date_published": "2026-03-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/5-architecture-tests-you-should-add-to-your-dotnet-projects",
            "content_html": "<p>Architecture tests turn rules like layer boundaries, naming conventions, and dependency direction into xUnit tests that fail the build when someone breaks them.\nYou write them with <code>ArchUnitNET</code> using a fluent API, and they run in milliseconds without any infrastructure.\nThe five I add to every .NET project cover layer dependencies, naming, colocation, visibility, and third-party dependency guards.</p>\n<p>Every project starts with good intentions.\nYou agree on layer boundaries, naming conventions, dependency direction.\nThe diagram goes on Confluence.</p>\n<p>Six months later, someone moves a domain service into the <code>Infrastructure</code> project because it needs database access.\nOr a handler gets named <code>ProcessPaymentService</code> because the dev didn't know the convention.\nOr a class that should be <code>internal</code> is <code>public</code> because that's the default.\nNobody catches it in code review.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/shift-left-with-architecture-testing-in-dotnet\"><strong>Architecture tests</strong></a> stop this from happening.\nThey turn your architectural rules into automated tests that run in CI.\nIf someone violates a rule, the build fails.</p>\n<p>Here are 5 types of architecture tests I add to every .NET project.</p>\n<h2>ArchUnitNET and the Test Setup</h2>\n<p><a href=\"https://github.com/TNG/ArchUnitNET\">ArchUnitNET</a> is the .NET port of the Java <a href=\"https://www.archunit.org/\">ArchUnit</a> library.\nIt lets you write <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>architecture rules</strong></a>\nusing a fluent API and run them as regular xUnit tests.</p>\n<pre><code class=\"language-bash\"># there are other test frameworks supported, but I use xUnit\ndotnet add package TngTech.ArchUnitNET.xUnit\n</code></pre>\n<p>You need a base class that loads all the assemblies you want to test.\nEach layer gets an &quot;anchor type&quot; to grab a reference to the assembly at compile time:</p>\n<pre><code class=\"language-csharp\">public abstract class BaseTest\n{\n    protected static readonly Assembly DomainAssembly = typeof(User).Assembly;\n    protected static readonly Assembly ApplicationAssembly = typeof(ICommand).Assembly;\n    protected static readonly Assembly InfrastructureAssembly = typeof(ApplicationDbContext).Assembly;\n    protected static readonly Assembly PresentationAssembly = typeof(Program).Assembly;\n\n    protected static readonly Architecture Architecture = new ArchLoader()\n        .LoadAssemblies(\n            DomainAssembly,\n            ApplicationAssembly,\n            InfrastructureAssembly,\n            PresentationAssembly)\n        .Build();\n}\n</code></pre>\n<p>The <code>ArchLoader</code> scans these assemblies and builds an in-memory model of all types and their dependencies.\nEvery test class inherits from <code>BaseTest</code>.</p>\n<h2>1. Layer Dependency Tests</h2>\n<p>The most important rule in <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture</strong></a> is the <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>dependency rule</strong></a>.\nInner layers must not reference outer layers.\nAll dependencies point inward.</p>\n<p>In most Clean Architecture setups, the project references already prevent the obvious violations.\nYou can't add a reference from <code>Application</code> to <code>Infrastructure</code>\nbecause <code>Infrastructure</code> already references <code>Application</code>, and the compiler won't allow circular dependencies.</p>\n<p>So why bother with these tests?</p>\n<p>Because project references aren't the only way dependencies leak in.\nA NuGet package used in Infrastructure might expose types that bleed into Application through transitive references.\nSomeone could reorganize the solution and change the project reference graph.\nOr you might move to a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>\nwhere multiple layers share an assembly, and the compiler can't help you at all.</p>\n<p>These tests are a safety net.\nAnd they double as living documentation of the intended architecture.</p>\n<pre><code class=\"language-csharp\">using static ArchUnitNET.Fluent.ArchRuleDefinition;\n\npublic class LayerTests : BaseTest\n{\n    private static readonly IObjectProvider&lt;IType&gt; DomainLayer =\n        Types().That().ResideInAssembly(DomainAssembly).As(&quot;Domain layer&quot;);\n\n    private static readonly IObjectProvider&lt;IType&gt; ApplicationLayer =\n        Types().That().ResideInAssembly(ApplicationAssembly).As(&quot;Application layer&quot;);\n\n    private static readonly IObjectProvider&lt;IType&gt; InfrastructureLayer =\n        Types().That().ResideInAssembly(InfrastructureAssembly).As(&quot;Infrastructure Layer&quot;);\n\n    private static readonly IObjectProvider&lt;IType&gt; PresentationLayer =\n        Types().That().ResideInAssembly(PresentationAssembly).As(&quot;Presentation Layer&quot;);\n\n    [Fact]\n    public void DomainLayer_ShouldNotDependOn_ApplicationLayer()\n    {\n        Types().That().Are(DomainLayer).Should()\n            .NotDependOnAny(ApplicationLayer)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void DomainLayer_ShouldNotDependOn_InfrastructureLayer()\n    {\n        Types().That().Are(DomainLayer).Should()\n            .NotDependOnAny(InfrastructureLayer)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void DomainLayer_ShouldNotDependOn_PresentationLayer()\n    {\n        Types().That().Are(DomainLayer).Should()\n            .NotDependOnAny(PresentationLayer)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void ApplicationLayer_ShouldNotDependOn_InfrastructureLayer()\n    {\n        Types().That().Are(ApplicationLayer).Should()\n            .NotDependOnAny(InfrastructureLayer)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void ApplicationLayer_ShouldNotDependOn_PresentationLayer()\n    {\n        Types().That().Are(ApplicationLayer).Should()\n            .NotDependOnAny(PresentationLayer)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void InfrastructureLayer_ShouldNotDependOn_PresentationLayer()\n    {\n        Types().That().Are(InfrastructureLayer).Should()\n            .NotDependOnAny(PresentationLayer)\n            .Check(Architecture);\n    }\n}\n</code></pre>\n<p>Six tests, one for each illegal dependency direction.</p>\n<p>The fluent API reads like English:\n&quot;Types that are in the Domain layer should not depend on any types in the Application layer&quot;.\nWhen a violation happens, the test tells you exactly which type depends on which.</p>\n<p>This is the first architecture test I add to any project.\nIf you only add one type from this list, make it this one.</p>\n<p>You can also extend this further.\nFor example, I sometimes add tests that specific namespaces within a layer can't reference each other\n(like <code>Application.Orders</code> shouldn't depend on <code>Application.Users</code>).\nThis can be great for enforcing a <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>vertical slice architecture</strong></a>\nwhere each feature is self-contained, or inside a <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>modular monolith</strong></a>\nwhere modules shouldn't depend on each other.</p>\n<h2>2. Naming Convention Tests</h2>\n<p>This one might seem minor, but it adds up fast.</p>\n<p>When you have 50 command handlers and 3 of them are named <code>CreateOrderService</code> or <code>ProcessPaymentUseCase</code>,\nsearching the codebase becomes unreliable. You search for <code>CommandHandler</code> and miss three handlers.</p>\n<p>ArchUnitNET lets you enforce naming rules by selecting classes based on the interfaces they implement:</p>\n<pre><code class=\"language-csharp\">using static ArchUnitNET.Fluent.ArchRuleDefinition;\n\npublic class NamingConventionTests : BaseTest\n{\n    [Fact]\n    public void CommandHandlers_ShouldHave_NameEndingWith_CommandHandler()\n    {\n        Classes().That()\n            .ImplementInterface(typeof(ICommandHandler&lt;&gt;))\n            .Or()\n            .ImplementInterface(typeof(ICommandHandler&lt;,&gt;))\n            .And().DoNotResideInNamespace(&quot;Application.Abstractions.Behaviors&quot;)\n            .Should().HaveNameEndingWith(&quot;CommandHandler&quot;)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void QueryHandlers_ShouldHave_NameEndingWith_QueryHandler()\n    {\n        Classes().That()\n            .ImplementInterface(typeof(IQueryHandler&lt;,&gt;))\n            .And().DoNotResideInNamespace(&quot;Application.Abstractions.Behaviors&quot;)\n            .Should().HaveNameEndingWith(&quot;QueryHandler&quot;)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void Validators_ShouldHave_NameEndingWith_Validator()\n    {\n        Classes().That()\n            .HaveNameEndingWith(&quot;Validator&quot;)\n            .Should().ResideInAssembly(ApplicationAssembly)\n            .Check(Architecture);\n    }\n}\n</code></pre>\n<p>One gotcha here: decorators like <code>ValidationBehavior</code> implement handler interfaces too.\nThey're decorators in the pipeline, not domain-specific handlers.\nThe <code>DoNotResideInNamespace(&quot;Application.Abstractions.Behaviors&quot;)</code> filter excludes them.\nI learned this the hard way when every behavior started failing the naming check.</p>\n<p>The validator test works in the opposite direction.\nIt says &quot;classes ending with <code>Validator</code> should live in the Application assembly&quot;.\nI've seen validators accidentally placed in the Infrastructure project.\nThis catches that.</p>\n<h2>3. Colocation Tests</h2>\n<p>This is the test I wish I had earlier in my career.</p>\n<p>When you use <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-the-way-it-should-have-been-from-the-start\"><strong>CQRS</strong></a>,\nyou end up with pairs: a command (or query) and its handler.\nI keep them in the same namespace so everything for a use case lives together.\n<code>Application.TodoItems.Create</code> would contain both <code>CreateTodoItemCommand</code> and <code>CreateTodoItemCommandHandler</code>.</p>\n<p>But nothing stops someone from putting the handler in a completely different namespace:</p>\n<pre><code class=\"language-csharp\">public class ColocationTests : BaseTest\n{\n    [Theory]\n    [MemberData(nameof(GetHandlerAndCommandPairs))]\n    public void Handlers_ShouldResideInSameNamespace_AsTheirCommandOrQuery(\n        Type handlerType,\n        Type commandOrQueryType)\n    {\n        handlerType.Namespace.ShouldBe(\n            commandOrQueryType.Namespace,\n            $&quot;{handlerType.Name} should be in the same namespace as {commandOrQueryType.Name}&quot;);\n    }\n\n    public static TheoryData&lt;Type, Type&gt; GetHandlerAndCommandPairs()\n    {\n        Type[] handlerInterfaces =\n        [\n            typeof(ICommandHandler&lt;&gt;),\n            typeof(ICommandHandler&lt;,&gt;),\n            typeof(IQueryHandler&lt;,&gt;)\n        ];\n\n        var pairs = new TheoryData&lt;Type, Type&gt;();\n\n        IEnumerable&lt;Type&gt; handlers = ApplicationAssembly\n            .GetTypes()\n            .Where(t =&gt; t is { IsClass: true, IsAbstract: false, IsGenericTypeDefinition: false })\n            .Where(t =&gt; t.DeclaringType is null);\n\n        foreach (Type handler in handlers)\n        {\n            foreach (Type iface in handler.GetInterfaces())\n            {\n                if (!iface.IsGenericType)\n                {\n                    continue;\n                }\n\n                Type genericDef = iface.GetGenericTypeDefinition();\n\n                if (!handlerInterfaces.Contains(genericDef))\n                {\n                    continue;\n                }\n\n                Type commandOrQueryType = iface.GetGenericArguments()[0];\n                pairs.Add(handler, commandOrQueryType);\n            }\n        }\n\n        return pairs;\n    }\n}\n</code></pre>\n<p>This test doesn't use ArchUnitNET.\nIt uses <strong>raw reflection</strong> combined with xUnit's <code>[Theory]</code> and <code>[MemberData]</code>.\nArchUnitNET can't express a rule like\n&quot;this class should be in the same namespace as the generic type argument of its interface&quot;.\nSo we drop down to reflection.</p>\n<p>The <code>GetHandlerAndCommandPairs</code> method scans the Application assembly,\nfinds all classes implementing a handler interface,\nextracts the command/query type from the generic argument,\nand returns pairs for the test to assert on.</p>\n<p>This enforces a <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\"><strong>vertical slice</strong></a> style of organizing code.\nWhen a new developer joins the team, they can find everything for a use case in one folder.</p>\n<p>You can expand this to cover request and response types, validators, or anything else that should be colocated.</p>\n<h2>4. Visibility Tests</h2>\n<p>Command and query handlers are implementation details.\nThey get resolved through DI, not referenced directly.</p>\n<p>But most developers make them <code>public</code> by default.\nIt's just muscle memory.\nThe problem is that a <code>public</code> handler can be referenced directly from another layer,\nbypassing the abstractions you set up.</p>\n<pre><code class=\"language-csharp\">using static ArchUnitNET.Fluent.ArchRuleDefinition;\n\npublic class VisibilityTests : BaseTest\n{\n    [Fact]\n    public void CommandHandlers_ShouldBeInternal()\n    {\n        Classes().That()\n            .ImplementInterface(typeof(ICommandHandler&lt;&gt;))\n            .Or()\n            .ImplementInterface(typeof(ICommandHandler&lt;,&gt;))\n            .Should().BeInternal()\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void QueryHandlers_ShouldBeInternal()\n    {\n        Classes().That()\n            .ImplementInterface(typeof(IQueryHandler&lt;,&gt;))\n            .Should().BeInternal()\n            .Check(Architecture);\n    }\n}\n</code></pre>\n<p>If you're worried about DI not finding <code>internal</code> classes, don't be.\nAssembly scanning discovers them just fine.</p>\n<p>You could extend this to other types too.\nI've thought about enforcing that EF Core configurations are <code>internal</code> as well,\nsince there's no reason for <code>OrderConfiguration</code> to be visible outside of Infrastructure.</p>\n<h2>5. Dependency Guard Tests</h2>\n<p>Layer tests guard against references to your own assemblies.\nBut infrastructure libraries can leak in through transitive NuGet references.</p>\n<p>Your Domain layer shouldn't know about Entity Framework. Your Application layer shouldn't know about Npgsql.\nThe compiler won't stop this if the package is transitively available.</p>\n<pre><code class=\"language-csharp\">using static ArchUnitNET.Fluent.ArchRuleDefinition;\n\npublic class DependencyGuardTests : BaseTest\n{\n    [Fact]\n    public void DomainLayer_ShouldNotDependOn_EntityFramework()\n    {\n        Types().That().ResideInAssembly(DomainAssembly).Should()\n            .NotDependOnAnyTypesThat()\n            .ResideInNamespace(&quot;Microsoft.EntityFrameworkCore&quot;)\n            .Check(Architecture);\n    }\n\n    [Fact]\n    public void ApplicationLayer_ShouldNotDependOn_EntityFramework()\n    {\n        Types().That().ResideInAssembly(ApplicationAssembly).Should()\n            .NotDependOnAnyTypesThat()\n            .ResideInNamespace(&quot;Microsoft.EntityFrameworkCore&quot;)\n            .Check(Architecture);\n    }\n}\n</code></pre>\n<p>Add whatever libraries make sense for your project.</p>\n<h2>Summary</h2>\n<p>Architectural rules that only exist in documentation will be violated.\nIt's not a question of <em>if</em>, it's <em>when</em>.</p>\n<p>All of these tests run in milliseconds and don't require any infrastructure to run.\nThey sit right next to your <a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>unit tests</strong></a> and run on every build.</p>\n<p>Architecture tests are a safety net that catches violations before they reach production.</p>\n<p>Start with layer dependency tests.\nThey take five minutes to set up and catch the most damaging violations.\nThen add the rest as your codebase grows.</p>\n<p>If you need rules these five can't express, like proving your feature slices contain no dependency cycles, I covered that in <a href=\"https://milanjovanovic.tech/blog/architecture-fitness-functions-archunitnet\"><strong>architecture fitness functions with ArchUnitNET</strong></a>.</p>\n<p>If you want to see how I structure Clean Architecture projects with these guardrails,\ncheck out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/5-architecture-tests-you-should-add-to-your-dotnet-projects",
            "title": "5 Architecture Tests You Should Add to Your .NET Projects",
            "summary": "Learn about five essential architecture tests that can help ensure the quality and maintainability of your .NET projects.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_184.png",
            "date_modified": "2026-03-07T00:00:00.000Z",
            "date_published": "2026-03-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-implement-two-factor-authentication-in-aspnetcore",
            "content_html": "<p>Two-factor authentication in ASP.NET Core comes down to TOTP: generate a random secret with <code>Otp.NET</code>, encode it as Base32, and render an <code>otpauth://</code> URI as a QR code the user scans.\nActivate 2FA only after the user submits a valid first code, and encrypt the secret at rest.</p>\n<p>I got hit with a <a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7432473773032808448/\">security incident</a> recently.\nSomeone accessed an account of mine that had a strong, unique password.\nBut no second factor.</p>\n<p>It was a wake-up call.\nPasswords alone are not enough.\nThey get phished, leaked in breaches, or brute-forced.\nA second factor changes the equation entirely.</p>\n<p><strong>Two-Factor Authentication (2FA)</strong> adds an extra verification step beyond the password.\nEven if an attacker steals the password, they still can't get in without the second factor.\nIt's one of the most effective security measures you can implement, and it's not that hard to build.</p>\n<p>In this article, I'll walk you through implementing 2FA in .NET using <strong>Time-based One-Time Passwords (TOTP)</strong> with QR codes and authenticator apps like Google Authenticator.</p>\n<p>We'll cover:</p>\n<ul>\n<li>How TOTP works under the hood</li>\n<li>Generating QR codes for authenticator app setup</li>\n<li>The correct setup flow to avoid putting users in a bad state</li>\n<li>Validating one-time codes</li>\n<li>Encrypting user secrets at rest</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>How TOTP Works</h2>\n<p><a href=\"https://www.rfc-editor.org/rfc/rfc6238\">TOTP (Time-based One-Time Password)</a> is the algorithm behind apps like Google Authenticator, Microsoft Authenticator, and Authy.</p>\n<p>The idea is simple: a <strong>shared secret</strong> is established between the server and the user's authenticator app.\nBoth sides use that secret combined with the current time to generate a <strong>6-digit code</strong> that changes every 30 seconds.</p>\n<p>Here's the flow:</p>\n<ol>\n<li>The server generates a <strong>unique secret key</strong> for the user</li>\n<li>The user scans a <strong>QR code</strong> containing that secret into their authenticator app</li>\n<li>Both the server and the app now independently generate the same time-based codes</li>\n<li>At login, the user enters the current code from their app, and the server verifies it</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_183/totp_flow.png\" alt=\"TOTP flow showing how the server and authenticator app independently generate the same time-based code from a shared secret.\">\n<p>Because both sides compute the code independently, there's <strong>no network call</strong> to validate.\nThe server just checks: &quot;given this secret and the current time, does the code match?&quot;</p>\n<p>This makes TOTP fast, offline-capable, and resistant to replay attacks (each code is only valid for a short window).</p>\n<h2>Generating the Secret Key</h2>\n<p>Every user needs their own unique secret key.\nThis key is the foundation of the entire 2FA system, so it must be cryptographically random.</p>\n<p>We'll use the <a href=\"https://github.com/kspearrin/Otp.NET\">Otp.NET</a> library for TOTP operations:</p>\n<pre><code class=\"language-bash\">dotnet add package Otp.NET\n</code></pre>\n<p>Generate a secret key for a user:</p>\n<pre><code class=\"language-csharp\">using OtpNet;\n\nbyte[] secretKey = KeyGeneration.GenerateRandomKey(); // 20 bytes by default (SHA-1)\nstring base32Secret = Base32Encoding.ToString(secretKey);\n</code></pre>\n<p><code>KeyGeneration.GenerateRandomKey()</code> produces a cryptographically secure random key.\nWe encode it as <strong>Base32</strong> because that's what the <a href=\"https://github.com/google/google-authenticator/wiki/Key-Uri-Format\">otpauth URI scheme</a> expects.</p>\n<p><strong>This secret must be stored securely.</strong>\nIt's the equivalent of a password.\nIf an attacker gets the secret, they can generate valid codes.\nI'll cover encrypting it at rest later in this article.</p>\n<h2>Creating the QR Code</h2>\n<p>To set up 2FA, the user needs to scan a QR code with their authenticator app.\nThe QR code encodes an <code>otpauth://</code> URI that contains the secret key and metadata.</p>\n<p>Install the <a href=\"https://github.com/codebude/QRCoder\">QRCoder</a> library:</p>\n<pre><code class=\"language-bash\">dotnet add package QRCoder\n</code></pre>\n<p>Here's how to generate the QR code:</p>\n<pre><code class=\"language-csharp\">using QRCoder;\n\nconst string issuer = &quot;MyApp&quot;;\nconst string user = &quot;user@example.com&quot;;\n\nstring escapedIssuer = Uri.EscapeDataString(issuer);\nstring escapedUser = Uri.EscapeDataString(user);\n\nstring otpUri =\n    $&quot;otpauth://totp/{escapedIssuer}:{escapedUser}&quot; +\n    $&quot;?secret={base32Secret}&quot; +\n    $&quot;&amp;issuer={escapedIssuer}&quot; +\n    $&quot;&amp;digits=6&quot; +\n    $&quot;&amp;period=30&quot;;\n\nusing var qrGenerator = new QRCodeGenerator();\nusing var qrCodeData = qrGenerator.CreateQrCode(otpUri, QRCodeGenerator.ECCLevel.Q);\nusing var qrCode = new PngByteQRCode(qrCodeData);\nbyte[] qrCodeImage = qrCode.GetGraphic(10);\n</code></pre>\n<p>Let's unpack the <code>otpauth://</code> URI parameters:</p>\n<ul>\n<li><strong><code>secret</code></strong> - The Base32-encoded shared secret</li>\n<li><strong><code>issuer</code></strong> - Your application name (shown in the authenticator app)</li>\n<li><strong><code>digits</code></strong> - Number of digits in the code (standard is 6)</li>\n<li><strong><code>period</code></strong> - How often the code rotates in seconds (standard is 30)</li>\n</ul>\n<p>The <code>ECCLevel.Q</code> gives us a good balance between error correction and QR code size.\nIt means the QR code can still be scanned even if about 25% of it is damaged or obscured.</p>\n<p>Here's what the generated QR code looks like:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_183/qr_code_example.png\" alt=\"A generated QR code encoding the otpauth URI for authenticator app setup.\">\n</div>\n<p>And once the user scans it, the entry appears in their authenticator app:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_183/google_authenticator_entry.png\" alt=\"Google Authenticator showing a TOTP entry for MyApp with a 6-digit code and a 30-second countdown timer.\">\n</div>\n<p>You can also display the <code>base32Secret</code> string alongside the QR code.\nSome users prefer to type it in manually.</p>\n<h2>The Setup Flow</h2>\n<p>Getting the setup flow right is critical.\nIf you enable 2FA the moment the user requests it, before they've even scanned the QR code, you've locked them out.</p>\n<p>Here's the correct flow:</p>\n<ol>\n<li><strong>User requests 2FA setup</strong> - Generate a secret key and store it as <em>pending</em> (not yet active)</li>\n<li><strong>Show the QR code</strong> - The user scans it with their authenticator app</li>\n<li><strong>User enters the first code</strong> - This proves they successfully set up their authenticator app</li>\n<li><strong>Server validates the code</strong> - If it matches, activate 2FA for the user</li>\n<li><strong>Generate recovery codes</strong> - Give the user backup codes in case they lose their device</li>\n</ol>\n<p>The key insight is step 3.\n<strong>Never enable 2FA until the user has confirmed they can generate valid codes.</strong>\nOtherwise, you'll end up with users who have 2FA &quot;enabled&quot; but no way to generate codes.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_183/setup_flow.png\" alt=\"Sequence diagram showing the 2FA setup flow: request setup, scan QR code, confirm first code, activate.\">\n<p>Here's what the API endpoints look like.\nAll 2FA endpoints must be <strong>protected</strong>, the user has to be authenticated first.\nThe best practice is to use <code>.RequireAuthorization()</code> on each endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;2fa/setup&quot;, async (HttpContext context, UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    byte[] secretKey = KeyGeneration.GenerateRandomKey();\n    string base32Secret = Base32Encoding.ToString(secretKey);\n\n    // Store the pending secret (encrypted) - NOT yet active\n    await userService.StorePendingTwoFactorSecret(userId, base32Secret);\n\n    string otpUri =\n        $&quot;otpauth://totp/{Uri.EscapeDataString(&quot;MyApp&quot;)}:{Uri.EscapeDataString(userId)}&quot; +\n        $&quot;?secret={base32Secret}&quot; +\n        $&quot;&amp;issuer={Uri.EscapeDataString(&quot;MyApp&quot;)}&quot; +\n        $&quot;&amp;digits=6&amp;period=30&quot;;\n\n    using var qrGenerator = new QRCodeGenerator();\n    using var qrCodeData = qrGenerator.CreateQrCode(otpUri, QRCodeGenerator.ECCLevel.Q);\n    using var qrCode = new PngByteQRCode(qrCodeData);\n    byte[] qrCodeImage = qrCode.GetGraphic(10);\n\n    return Results.File(qrCodeImage, &quot;image/png&quot;);\n})\n.RequireAuthorization();\n</code></pre>\n<p>And the confirmation endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;2fa/confirm&quot;, async (\n    ConfirmTwoFactorRequest request,\n    HttpContext context,\n    UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    string? pendingSecret = await userService.GetPendingTwoFactorSecret(userId);\n    if (pendingSecret is null)\n    {\n        return Results.BadRequest(&quot;No pending 2FA setup found.&quot;);\n    }\n\n    byte[] secretKey = Base32Encoding.ToBytes(pendingSecret);\n    var totp = new Totp(secretKey);\n\n    bool isValid = totp.VerifyTotp(\n        request.Code,\n        out _,\n        VerificationWindow.RfcSpecifiedNetworkDelay);\n\n    if (!isValid)\n    {\n        return Results.BadRequest(&quot;Invalid code. Please try again.&quot;);\n    }\n\n    // Code is valid - activate 2FA\n    await userService.ActivateTwoFactor(userId, pendingSecret);\n\n    // Generate recovery codes\n    var recoveryCodes = await userService.GenerateRecoveryCodes(userId);\n\n    return Results.Ok(new { recoveryCodes });\n})\n.RequireAuthorization();\n\ninternal record ConfirmTwoFactorRequest(string Code);\n</code></pre>\n<p>This two-step approach guarantees you never activate 2FA for a user who can't actually use it.\nIf the user abandons the setup halfway through, the pending secret gets cleaned up and nothing breaks.</p>\n<h2>The Login Flow With 2FA</h2>\n<p>Here's a critical point that's easy to get wrong: <strong>don't issue a full access token until the user passes the 2FA check.</strong></p>\n<p>If a user has 2FA enabled and you issue a <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT</strong></a> after they enter their password, you've already given them full access.\nThe 2FA step becomes meaningless.</p>\n<p>The correct approach is a <strong>two-step login</strong>:</p>\n<ol>\n<li>The user submits their username and password</li>\n<li>If credentials are valid and 2FA is enabled, return a <strong>limited-scope token</strong> (or session) that only allows calling the <code>2fa/validate</code> endpoint</li>\n<li>The user submits their TOTP code</li>\n<li>If the code is valid, issue the <strong>full access token</strong></li>\n</ol>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;auth/login&quot;, async (LoginRequest request, UserService userService) =&gt;\n{\n    var user = await userService.ValidateCredentials(request.Email, request.Password);\n    if (user is null)\n    {\n        return Results.Unauthorized();\n    }\n\n    if (user.TwoFactorEnabled)\n    {\n        // Issue a short-lived, limited token that only permits 2FA validation\n        var limitedToken = TokenService.GenerateLimitedToken(user.Id, purpose: &quot;2fa&quot;);\n\n        return Results.Ok(new { requiresTwoFactor = true, token = limitedToken });\n    }\n\n    // No 2FA - issue full access token\n    var accessToken = TokenService.GenerateAccessToken(user);\n\n    return Results.Ok(new { accessToken });\n});\n</code></pre>\n<p>The limited token should have a short expiration (2-3 minutes) and a claim or scope that restricts it to the <code>2fa/validate</code> endpoint only.\nYour authorization policy on the validation endpoint can check for this specific claim.</p>\n<p>This way, a stolen password alone never results in a full access token.</p>\n<h2>Validating TOTP Codes</h2>\n<p>Once 2FA is active, you need to validate codes during login.\nHere's the validation logic:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;2fa/validate&quot;, async (\n    ValidateOtpRequest request,\n    HttpContext context,\n    UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    string? secret = await userService.GetTwoFactorSecret(userId);\n    if (secret is null)\n    {\n        return Results.BadRequest(&quot;2FA is not enabled.&quot;);\n    }\n\n    byte[] secretKey = Base32Encoding.ToBytes(secret);\n    var totp = new Totp(secretKey);\n\n    bool isValid = totp.VerifyTotp(\n        request.Code,\n        out long timeStepMatched,\n        VerificationWindow.RfcSpecifiedNetworkDelay);\n\n    return Results.Ok(new { isValid });\n})\n.RequireAuthorization();\n\ninternal record ValidateOtpRequest(string Code);\n</code></pre>\n<p>The <code>VerificationWindow.RfcSpecifiedNetworkDelay</code> parameter is important.\nIt allows a small window of tolerance around the current time step.\nThis accounts for clock drift between the server and the user's device.</p>\n<p>Without a verification window, a code that was valid 2 seconds ago might be rejected because the server crossed into the next 30-second period.\nThe RFC-specified window typically allows one time step before and after the current one.</p>\n<h3>Preventing Code Reuse</h3>\n<p>One subtle but important point: <strong>a TOTP code should only be accepted once</strong>.</p>\n<p>If an attacker intercepts a valid code (e.g., through shoulder surfing), they shouldn't be able to reuse it.\nThe <code>timeStepMatched</code> output parameter tells you which time step the code belongs to.\nYou can store the last used time step and reject any code from the same or earlier step:</p>\n<pre><code class=\"language-csharp\">bool isValid = totp.VerifyTotp(\n    request.Code,\n    out long timeStepMatched,\n    VerificationWindow.RfcSpecifiedNetworkDelay);\n\nif (isValid)\n{\n    long? lastUsedTimeStep = await userService.GetLastUsedTimeStep(userId);\n\n    if (lastUsedTimeStep.HasValue &amp;&amp; timeStepMatched &lt;= lastUsedTimeStep.Value)\n    {\n        return Results.BadRequest(&quot;Code already used.&quot;);\n    }\n\n    await userService.UpdateLastUsedTimeStep(userId, timeStepMatched);\n}\n</code></pre>\n<p>This prevents replay attacks within the verification window.</p>\n<h3>Rate Limiting</h3>\n<p>The validation endpoint is a brute-force target.\nA 6-digit code has only 1,000,000 possible combinations.\nWithout rate limiting, an attacker could try all of them in minutes.</p>\n<p>At a minimum, you should:</p>\n<ul>\n<li><strong>Limit attempts per user</strong> - Lock the account or add a delay after 3-5 failed attempts</li>\n<li><strong>Use exponential backoff</strong> - Double the wait time after each failure</li>\n<li><strong>Log failed attempts</strong> - Unusual patterns (many failures from one IP) are a red flag</li>\n</ul>\n<p>ASP.NET Core has a built-in <a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core\"><strong>rate limiting middleware</strong></a> that makes this straightforward to add.</p>\n<h2>Encrypting Secrets at Rest</h2>\n<p>The TOTP secret key is the most sensitive piece of data in your 2FA system.\nIf someone dumps your database, they shouldn't be able to generate valid codes for your users.</p>\n<p><strong>Never store TOTP secrets in plain text.</strong></p>\n<p>Encrypt them before writing to the database and decrypt only when you need to verify a code.\nI covered this in detail in my article on <a href=\"https://milanjovanovic.tech/blog/implementing-aes-encryption-with-csharp\"><strong>implementing AES encryption with C#</strong></a>.</p>\n<p>Here's the general approach:</p>\n<pre><code class=\"language-csharp\">public class UserService\n{\n    private readonly IEncryptionService _encryptionService;\n\n    public async Task StorePendingTwoFactorSecret(string userId, string secret)\n    {\n        string encryptedSecret = _encryptionService.Encrypt(secret);\n\n        // Store encryptedSecret in the database\n        await _dbContext.Users\n            .Where(u =&gt; u.Id == userId)\n            .ExecuteUpdateAsync(u =&gt; u\n                .SetProperty(x =&gt; x.PendingTwoFactorSecret, encryptedSecret));\n    }\n\n    public async Task&lt;string?&gt; GetTwoFactorSecret(string userId)\n    {\n        var user = await _dbContext.Users.FindAsync(userId);\n        if (user?.TwoFactorSecret is null) return null;\n\n        return _encryptionService.Decrypt(user.TwoFactorSecret);\n    }\n}\n</code></pre>\n<p>The encryption key itself should live in a <strong>key management service</strong> like\n<a href=\"https://learn.microsoft.com/en-us/azure/key-vault/\">Azure Key Vault</a>,\n<a href=\"https://aws.amazon.com/kms/\">AWS KMS</a>, or\n<a href=\"https://www.vaultproject.io/\">HashiCorp Vault</a>.\nNever store it in your <code>appsettings.json</code> or source code.</p>\n<h2>Recovery Codes</h2>\n<p>What happens when a user loses their phone?</p>\n<p>Without a recovery mechanism, they're permanently locked out of their account.\n<strong>Recovery codes</strong> solve this.\nThey're one-time-use codes generated when the user enables 2FA.</p>\n<pre><code class=\"language-csharp\">public async Task&lt;List&lt;string&gt;&gt; GenerateRecoveryCodes(string userId, int count = 8)\n{\n    var codes = new List&lt;string&gt;();\n\n    for (int i = 0; i &lt; count; i++)\n    {\n        // Generate a cryptographically random code\n        var bytes = RandomNumberGenerator.GetBytes(5);\n        var code = Convert.ToHexString(bytes).ToLower();\n        codes.Add(code);\n    }\n\n    // Hash the codes before storing (same as passwords - one-way)\n    var hashedCodes = codes\n        .Select(c =&gt; BCrypt.Net.BCrypt.HashPassword(c))\n        .ToList();\n\n    await _dbContext.RecoveryCodes\n        .Where(rc =&gt; rc.UserId == userId)\n        .ExecuteDeleteAsync();\n\n    _dbContext.RecoveryCodes.AddRange(\n        hashedCodes.Select(h =&gt; new RecoveryCode\n        {\n            UserId = userId,\n            CodeHash = h,\n            IsUsed = false\n        }));\n\n    await _dbContext.SaveChangesAsync();\n\n    // Return plain text codes to show the user ONCE\n    return codes;\n}\n</code></pre>\n<p>A few important details:</p>\n<ul>\n<li><strong>Hash the recovery codes</strong> before storing them. They're single-use passwords.\nUse <code>bcrypt</code> (e.g. <a href=\"https://github.com/BcryptNet/bcrypt.net\">Bcrypt.Net</a>) or similar.</li>\n<li><strong>Show them only once.</strong> After the user dismisses the dialog, the plain text codes are gone.</li>\n<li><strong>Mark codes as used.</strong> Each recovery code works exactly once.</li>\n<li><strong>Generate enough codes.</strong> Eight to ten is standard. The user can regenerate them if they run low.</li>\n</ul>\n<p>When validating a recovery code, check each stored hash until you find a match:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;bool&gt; ValidateRecoveryCode(string userId, string code)\n{\n    var storedCodes = await _dbContext.RecoveryCodes\n        .Where(rc =&gt; rc.UserId == userId &amp;&amp; !rc.IsUsed)\n        .ToListAsync();\n\n    var matchingCode = storedCodes\n        .FirstOrDefault(rc =&gt; BCrypt.Net.BCrypt.Verify(code, rc.CodeHash));\n\n    if (matchingCode is null) return false;\n\n    matchingCode.IsUsed = true;\n    await _dbContext.SaveChangesAsync();\n\n    return true;\n}\n</code></pre>\n<h2>Putting It All Together</h2>\n<p>Here's a minimal but complete setup showing the full 2FA flow.\nI'm using a route group with <code>.RequireAuthorization()</code> so every endpoint underneath is protected:</p>\n<pre><code class=\"language-csharp\">using OtpNet;\nusing QRCoder;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddAuthentication().AddJwtBearer();\nbuilder.Services.AddAuthorization();\n\nvar app = builder.Build();\n\napp.UseAuthentication();\napp.UseAuthorization();\n\nvar twoFactorGroup = app.MapGroup(&quot;2fa&quot;).RequireAuthorization();\n\ntwoFactorGroup.MapPost(&quot;setup&quot;, async (HttpContext context, UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    byte[] secretKey = KeyGeneration.GenerateRandomKey();\n    string base32Secret = Base32Encoding.ToString(secretKey);\n\n    await userService.StorePendingTwoFactorSecret(userId, base32Secret);\n\n    string otpUri =\n        $&quot;otpauth://totp/{Uri.EscapeDataString(&quot;MyApp&quot;)}:{Uri.EscapeDataString(userId)}&quot; +\n        $&quot;?secret={base32Secret}&quot; +\n        $&quot;&amp;issuer={Uri.EscapeDataString(&quot;MyApp&quot;)}&quot; +\n        $&quot;&amp;digits=6&amp;period=30&quot;;\n\n    using var qrGenerator = new QRCodeGenerator();\n    using var qrCodeData = qrGenerator.CreateQrCode(otpUri, QRCodeGenerator.ECCLevel.Q);\n    using var qrCode = new PngByteQRCode(qrCodeData);\n    byte[] qrCodeImage = qrCode.GetGraphic(10);\n\n    return Results.File(qrCodeImage, &quot;image/png&quot;);\n});\n\ntwoFactorGroup.MapPost(&quot;confirm&quot;, async (\n    ConfirmTwoFactorRequest request,\n    HttpContext context,\n    UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    string? pendingSecret = await userService.GetPendingTwoFactorSecret(userId);\n    if (pendingSecret is null)\n    {\n        return Results.BadRequest(&quot;No pending 2FA setup found.&quot;);\n    }\n\n    byte[] secretKey = Base32Encoding.ToBytes(pendingSecret);\n    var totp = new Totp(secretKey);\n\n    bool isValid = totp.VerifyTotp(\n        request.Code,\n        out _,\n        VerificationWindow.RfcSpecifiedNetworkDelay);\n\n    if (!isValid)\n    {\n        return Results.BadRequest(&quot;Invalid code. Please try again.&quot;);\n    }\n\n    await userService.ActivateTwoFactor(userId, pendingSecret);\n    var recoveryCodes = await userService.GenerateRecoveryCodes(userId);\n\n    return Results.Ok(new { recoveryCodes });\n});\n\ntwoFactorGroup.MapPost(&quot;validate&quot;, async (\n    ValidateOtpRequest request,\n    HttpContext context,\n    UserService userService) =&gt;\n{\n    var userId = context.User.GetUserId();\n\n    string? secret = await userService.GetTwoFactorSecret(userId);\n    if (secret is null)\n    {\n        return Results.BadRequest(&quot;2FA is not enabled.&quot;);\n    }\n\n    byte[] secretKey = Base32Encoding.ToBytes(secret);\n    var totp = new Totp(secretKey);\n\n    bool isValid = totp.VerifyTotp(\n        request.Code,\n        out long timeStepMatched,\n        VerificationWindow.RfcSpecifiedNetworkDelay);\n\n    if (isValid)\n    {\n        long? lastUsedTimeStep = await userService.GetLastUsedTimeStep(userId);\n        if (lastUsedTimeStep.HasValue &amp;&amp; timeStepMatched &lt;= lastUsedTimeStep.Value)\n        {\n            return Results.BadRequest(&quot;Code already used.&quot;);\n        }\n\n        await userService.UpdateLastUsedTimeStep(userId, timeStepMatched);\n    }\n\n    return Results.Ok(new { isValid });\n});\n\napp.Run();\n\ninternal record ConfirmTwoFactorRequest(string Code);\ninternal record ValidateOtpRequest(string Code);\n</code></pre>\n<p>If you don't need full control, <a href=\"https://milanjovanovic.tech/blog/integrate-keycloak-with-aspnetcore-using-oauth-2\"><strong>Keycloak</strong></a> and\n<a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity\"><strong>ASP.NET Core Identity</strong></a>\nboth support TOTP-based 2FA out of the box.\nBut building it yourself is worth it when you need a custom flow or\nwant to understand what's happening under the hood.</p>\n<h2>Summary</h2>\n<p>2FA is one of the highest-impact security features you can add to an application.\nTOTP with authenticator apps is a solid choice because it's offline-capable, widely supported, and doesn't depend on SMS (which is vulnerable to SIM swapping).</p>\n<p>The important parts to get right:</p>\n<ul>\n<li><strong>Use a proper setup flow.</strong> Generate the secret, show the QR code, and only activate 2FA after the user confirms their first code.</li>\n<li><strong>Encrypt secrets at rest.</strong> The TOTP secret is as sensitive as a password. Encrypt it with <a href=\"https://milanjovanovic.tech/blog/implementing-aes-encryption-with-csharp\"><strong>AES</strong></a> and store keys in a key vault.</li>\n<li><strong>Prevent code reuse.</strong> Track the last used time step to block replay attacks.</li>\n<li><strong>Provide recovery codes.</strong> Users lose phones. Hash the codes before storing them, just like passwords.</li>\n</ul>\n<p>If you're building APIs that handle sensitive operations, adding 2FA significantly raises the bar for attackers.\nIt's not bulletproof, but it stops the vast majority of credential-based attacks.</p>\n<p>If you're looking for a deep dive into building secure APIs with authentication and encryption,\ncheck out my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a> course.</p>\n<p>Hope this was useful. See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-implement-two-factor-authentication-in-aspnetcore",
            "title": "How to Implement Two-Factor Authentication in ASP.NET Core",
            "summary": "Passwords alone are not enough. Learn how to implement Two-Factor Authentication in .NET using TOTP, QR codes, and the Otp.NET library, with a secure setup…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_183.png",
            "date_modified": "2026-02-28T00:00:00.000Z",
            "date_published": "2026-02-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/where-vertical-slices-fit-inside-the-modular-monolith-architecture",
            "content_html": "<p>Modules and vertical slices solve different problems.\nA module is a bounded context that owns its data and exposes a public API, while a vertical slice groups the request, handler, validation, and data access for one use case.\nModules are the macro decision, and each module independently picks its micro architecture, which is where vertical slices fit.</p>\n<p>Most teams get the macro architecture right.\nThey build a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a> with clear module boundaries, <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>public APIs</strong></a>, and proper <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>data isolation</strong></a>.</p>\n<p>But then they stop thinking about architecture.\nEvery module gets the same internal structure, usually some form of layered architecture.</p>\n<p>The thing is, <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture</strong></a> and <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>Vertical Slice Architecture</strong></a> aren't as far apart as people think.\nBoth focus on use cases and maximizing cohesion.\nClean Architecture just adds a rule for the direction of dependencies, which often leads to more abstractions and ceremony.\nMy <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> approach takes a middle ground, and it's quite similar to VSA in nature.</p>\n<p>The real question isn't which one is &quot;better&quot;.\nIt's where each one shines inside your modular monolith.\nAnd the beautiful part is: you can mix and match.</p>\n<h2>Two Levels of Architecture</h2>\n<p>There are two architectural decisions you need to make when building a modular monolith:</p>\n<ol>\n<li><strong>Macro architecture</strong> - How do you decompose the system into modules?\nThis covers module boundaries, <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>communication patterns</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>data isolation</strong></a>, public API design, and how modules are deployed.</li>\n<li><strong>Micro architecture</strong> - How do you organize code <em>inside</em> each module?\nThis covers folder structure, the direction of dependencies, how you implement use cases,\nwhere validation lives, and how you access the database.</li>\n</ol>\n<p>Most articles about modular monoliths focus entirely on the macro level.\nAnd for good reason.\nGetting <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>module boundaries</strong></a> wrong is <strong>expensive to fix</strong>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_182/modular_monolith.png\" alt=\"Modular monolith.\">\n<p>But the micro level is equally important.\nIt determines how easy it is to add features, navigate code, and onboard new developers within a module.\nIt's the architecture your team interacts with every day.</p>\n<p>The key insight is this:</p>\n<blockquote>\n<p>The macro architecture constrains how modules interact.\nThe micro architecture is a local decision each module can make independently.</p>\n</blockquote>\n<p>Your <code>Ticketing</code> module doesn't have to follow the same internal structure as your <code>Notifications</code> module.\nThe modular boundary gives you this freedom.</p>\n<h3>Vertical Slices Are Not Modules</h3>\n<p>I've seen people conflate vertical slices with modules.\nOn the macro level, a module can look like a &quot;vertical slice&quot; of the business domain.\nBut that analogy breaks down at the application level.</p>\n<p>A module is a <a href=\"https://milanjovanovic.tech/blog/bounded-context-ddd-explained\"><strong>bounded context</strong></a>.\nIt owns its data, exposes a public API, and encapsulates a business capability.\nA vertical slice is a feature implementation pattern.\nIt groups the request, handler, validation, and data access for a single use case.</p>\n<p>Modules and vertical slices operate at different levels.\nModules define the boundaries of the system.\nVertical slices organize the code within those boundaries.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_182/vertical_slices.png\" alt=\"Vertical slices.\">\n<h2>Vertical Slices Inside a Module</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\"><strong>Vertical Slice Architecture</strong></a>\norganizes code by feature instead of by technical layer.\nEach feature is a self-contained unit: request, handler, validation, data access, all in one place.</p>\n<p>Inside a modular monolith module, this is a natural fit.\nThe module boundary already enforces separation from the rest of the system.\nYou don't need layers to protect you.\nThe module's <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>public API</strong></a> does that.</p>\n<p>Here's what a <code>Ticketing</code> module looks like with vertical slices using one file per feature:</p>\n<pre><code>📁 Modules/\n|__ 📁 Ticketing\n    |__ 📁 Features\n        |__ 📁 AddItemToCart\n            |__ #️⃣ AddItemToCart.cs\n        |__ 📁 SubmitOrder\n            |__ #️⃣ SubmitOrder.cs\n        |__ 📁 GetOrder\n            |__ #️⃣ GetOrder.cs\n        |__ 📁 CancelOrder\n            |__ #️⃣ CancelOrder.cs\n        |__ 📁 RefundPayment\n            |__ #️⃣ RefundPayment.cs\n    |__ 📁 Data\n        |__ #️⃣ TicketingDbContext.cs\n    |__ 📁 Entities\n        |__ #️⃣ Order.cs\n        |__ #️⃣ Ticket.cs\n    |__ #️⃣ ITicketingModule.cs\n    |__ #️⃣ TicketingModule.cs\n</code></pre>\n<p>You can also use separate files per component if you prefer more granularity:</p>\n<pre><code>📁 Modules/\n|__ 📁 Ticketing\n    |__ 📁 Features\n        |__ 📁 SubmitOrder\n            |__ #️⃣ SubmitOrderRequest.cs\n            |__ #️⃣ SubmitOrderResponse.cs\n            |__ #️⃣ SubmitOrderHandler.cs\n            |__ #️⃣ SubmitOrderValidator.cs\n            |__ #️⃣ SubmitOrderEndpoint.cs\n        |__ 📁 GetOrder\n            |__ #️⃣ GetOrderRequest.cs\n            |__ #️⃣ GetOrderResponse.cs\n            |__ #️⃣ GetOrderHandler.cs\n            |__ #️⃣ GetOrderEndpoint.cs\n    |__ 📁 Data\n    |__ 📁 Entities\n    |__ #️⃣ ITicketingModule.cs\n    |__ #️⃣ TicketingModule.cs\n</code></pre>\n<p>Both approaches keep everything for a feature in one folder.\nCompare this to having <code>Application</code>, <code>Domain</code>, and <code>Infrastructure</code>\nfolders with dozens of files scattered across layers.\nThe vertical slice version is flat, scannable, and easy to navigate.</p>\n<p>Here's a concrete example using a static class to group the feature components:</p>\n<pre><code class=\"language-csharp\">public static class SubmitOrder\n{\n    public record Request(string CartId);\n    public record Response(string OrderId, decimal Total);\n\n    public class Validator : AbstractValidator&lt;Request&gt;\n    {\n        public Validator()\n        {\n            RuleFor(x =&gt; x.CartId).NotEmpty();\n        }\n    }\n\n    public class Endpoint : IEndpoint\n    {\n        public void MapEndpoint(IEndpointRouteBuilder app)\n        {\n            app.MapPost(&quot;orders&quot;, Handler).WithTags(&quot;Ticketing&quot;);\n        }\n\n        public static async Task&lt;IResult&gt; Handler(\n            Request request,\n            IValidator&lt;Request&gt; validator,\n            TicketingDbContext context)\n        {\n            var result = validator.Validate(request);\n            if (!result.IsValid)\n            {\n                return Results.BadRequest(result.Errors);\n            }\n\n            var cart = await context.Carts\n                .Include(c =&gt; c.Items)\n                .FirstOrDefaultAsync(c =&gt; c.Id == request.CartId);\n\n            if (cart is null)\n            {\n                return Results.NotFound();\n            }\n\n            var order = Order.Create(cart);\n\n            context.Orders.Add(order);\n            await context.SaveChangesAsync();\n\n            return Results.Ok(\n                new Response(order.Id, order.Total));\n        }\n    }\n}\n</code></pre>\n<p>Adding a new feature means adding a new folder.\nYou're not touching shared code across layers.\nYou're not worrying about side effects.</p>\n<h2>Choosing the Internal Architecture</h2>\n<p>A common misconception is that VSA is only for simple modules and Clean Architecture is for complex ones.\nThat's not how it works.</p>\n<p>Vertical slices work great with rich domain models.\nYou can have domain entities, value objects, and domain events inside a vertical slice.\nThe slice organizes the entry point and orchestration. The domain model handles the business rules.\nAs the slice grows in complexity, you <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>push logic into the domain</strong></a>\njust like you would in Clean Architecture.</p>\n<p>Similarly, <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> works well for simpler modules too.\nThe structure is lightweight when the domain is simple.</p>\n<p>So the decision isn't about complexity.\nIt's about what your team is comfortable with and what gives you the most clarity.</p>\n<p>Here are a few things I consider:</p>\n<ul>\n<li><strong>Team familiarity.</strong> If your team already thinks in layers and dependency direction,\nClean Architecture will feel natural. If they prefer organizing around features, VSA reduces friction.</li>\n<li><strong>Shared domain logic.</strong> When many use cases share the same domain entities,\nhaving a dedicated <code>Domain</code> layer can make that sharing explicit.\nWith VSA, you'd extract shared logic into a separate folder, which also works.</li>\n<li><strong>Independence of features.</strong> When features are mostly independent and rarely share behavior,\nvertical slices keep things simple. Each feature is self-contained, and the mental model is straightforward.</li>\n</ul>\n<p>The modular boundary protects the rest of the system regardless of what you choose inside the module.\nSo pick what makes your team most productive and be willing to change as the module evolves.</p>\n<h2>Takeaway</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>Modular Monolith</strong></a> answers the macro question:\nhow to decompose the system into modules with clear boundaries.\n<a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>Vertical Slice Architecture</strong></a> answers the micro question:\nhow to organize code by feature inside those modules.</p>\n<p>They operate at different levels.\nModular Monolith gives you high cohesion within each module and helps you manage coupling between modules.\nVertical Slice Architecture gives you high cohesion within each feature inside a module.</p>\n<p>You don't have to pick one internal architecture for the entire system.\nEach module can choose what works best for its context.\nSome modules will use <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.\nOthers will use vertical slices.\nA well-defined module boundary makes this safe.</p>\n<p>If you want to see how I build modular monoliths with this approach,\ncheck out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/where-vertical-slices-fit-inside-the-modular-monolith-architecture",
            "title": "Where Vertical Slices Fit Inside the Modular Monolith Architecture",
            "summary": "Modular Monolith tells you how to split the system into modules. But it says nothing about how to organize code inside each module.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_182.png",
            "date_modified": "2026-02-21T00:00:00.000Z",
            "date_published": "2026-02-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-extract-structured-data-from-images-using-ollama-in-dotnet",
            "content_html": "<p>You can run a vision model locally with Ollama and talk to it from .NET through <code>Microsoft.Extensions.AI</code>.\nAttach the image bytes as <code>DataContent</code> on a <code>ChatMessage</code>, then call <code>GetResponseAsync&lt;T&gt;</code> to get a strongly typed C# object back instead of raw JSON.\nMost of the accuracy work happens in the system prompt.</p>\n<p>I wanted to see how well a <strong>vision model</strong> (LLM) could parse grocery receipts into structured data.</p>\n<p>And I don't just mean describe what it sees.\nI want to be able to actually extract line items, quantities, and prices into clean JSON.\nRunning entirely on my machine with <a href=\"https://ollama.com/\"><strong>Ollama</strong></a> and a <strong>llama3.2-vision</strong> model.</p>\n<p>It started as a quick experiment. I ended up spending a whole evening on it.</p>\n<p>In this week's issue, I'll walk you through:</p>\n<ul>\n<li>Setting up Ollama with a vision model in .NET</li>\n<li>Sending images to the model and getting structured output</li>\n<li>Iterating on the system prompt to improve accuracy</li>\n<li>Deserializing LLM responses into strongly typed C# objects</li>\n<li>Testing whether the results are actually consistent</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>Setting Up Ollama With Microsoft.Extensions.AI</h2>\n<p><a href=\"https://ollama.com/\">Ollama</a> lets you run large language models locally.\nYou pull a model the same way you'd pull a Docker image, and it runs on your hardware.</p>\n<pre><code class=\"language-bash\">ollama pull llama3.2-vision:latest\n\n# Then run the model locally\nollama run llama3.2-vision:latest\n</code></pre>\n<p>On the .NET side, <a href=\"https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions\"><strong>Microsoft.Extensions.AI</strong></a>\nprovides a unified <code>IChatClient</code> interface for talking to any LLM provider.\nCombined with <a href=\"https://github.com/awaescher/OllamaSharp\">OllamaSharp</a>, the setup is minimal:</p>\n<pre><code class=\"language-csharp\">var builder = Host.CreateApplicationBuilder();\n\nbuilder.Services.AddChatClient(\n    new OllamaApiClient(\n        new Uri(&quot;http://localhost:11434&quot;),\n        &quot;llama3.2-vision:latest&quot;));\n\nvar app = builder.Build();\n\nvar chatClient = app.Services.GetRequiredService&lt;IChatClient&gt;();\n</code></pre>\n<p>That gives us an <code>IChatClient</code> backed by a local vision model.\nYou don't have to manage any API keys or cloud dependencies.</p>\n<p>The nice thing about <code>IChatClient</code> is that it's <strong>provider-agnostic</strong>.\nIf you want to swap Ollama for OpenAI or Azure later, your application code doesn't change.</p>\n<h2>Sending an Image to the Model</h2>\n<p>The simplest thing to try: send a receipt image and ask what's in it.</p>\n<pre><code class=\"language-csharp\">var message = new ChatMessage(\n    ChatRole.User, &quot;What's in this image?&quot;);\n\nmessage.Contents.Add(\n    new DataContent(\n        File.ReadAllBytes(&quot;receipts/receipt_1.png&quot;),\n        &quot;image/png&quot;));\n\nvar response = await chatClient.GetResponseAsync([message]);\n\nConsole.WriteLine(response.Text);\n</code></pre>\n<p>You read the image bytes, wrap them in a <code>DataContent</code> with the appropriate MIME type, and attach it to a <code>ChatMessage</code>.</p>\n<p>Here's the receipt I used for testing.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_181/receipt_1.png\" alt=\"A grocery receipt with line items, quantities, and prices.\">\n</div>\n<p>And here's the raw text response from the model:</p>\n<pre><code class=\"language-text\">This image appears to be a receipt or invoice in a foreign language, likely Russian or another\nSlavic language. The document is in black and white and features a large QR code at the bottom.\nThe text is in a blocky, old-style font and includes several columns of numbers and words.\nThe top of the page has a header with some information in a foreign language, followed by a\nseries of columns with various details such as date, time, and product information. The document\nalso includes some calculations and a total at the bottom. The overall design and layout suggest\nthat this is a receipt or invoice from a store or restaurant, but the specific details and\nlanguage make it difficult to understand without further context or translation.\n</code></pre>\n<p>The model correctly identified it as a receipt and listed the items on it.\nThat's impressive for a first try with zero fine-tuning.\nBut it's not very useful if we want to extract structured data.</p>\n<p>The good thing is we can refine our prompt and ask for a more specific output format.</p>\n<h2>Asking for JSON Output</h2>\n<p>A text description isn't very useful programmatically. So I asked for structured JSON instead:</p>\n<pre><code class=\"language-csharp\">var message = new ChatMessage(ChatRole.User,\n    @&quot;&quot;&quot;\n    Extract all line items from this receipt.\n    Respond in JSON format with this structure:\n    {\n        &quot;items&quot;: [\n            {\n                &quot;name&quot;: &quot;item name&quot;,\n                &quot;quantity&quot;: 1.500,\n                &quot;unitPrice&quot;: 0.00,\n                &quot;totalPrice&quot;: 0.00\n            }\n        ],\n        &quot;subtotal&quot;: 0.00\n    }\n    &quot;&quot;&quot;);\nmessage.Contents.Add(\n    new DataContent(\n        File.ReadAllBytes(&quot;receipts/receipt_1.png&quot;),\n        &quot;image/png&quot;));\n\nvar response = await chatClient.GetResponseAsync([message]);\n\nConsole.WriteLine(response.Text);\n</code></pre>\n<p>Here's the new response from the model:</p>\n<pre><code class=\"language-text\">Here is the receipt in JSON format with all line items extracted:\n\n{\n    &quot;items&quot;: [\n        {\n            &quot;name&quot;: &quot;limun /kg (A)&quot;,\n            &quot;quantity&quot;: 280.00,\n            &quot;unitPrice&quot;: 1.105,\n            &quot;totalPrice&quot;: 309.40\n        },\n        {\n            &quot;name&quot;: &quot;salata /kom (A)&quot;,\n            &quot;quantity&quot;: 70.00,\n            &quot;unitPrice&quot;: 3.00,\n            &quot;totalPrice&quot;: 210.00\n        },\n        {\n            &quot;name&quot;: &quot;susam 100g trpeza /kom (A)&quot;,\n            &quot;quantity&quot;: 90.00,\n            &quot;unitPrice&quot;: 1.00,\n            &quot;totalPrice&quot;: 90.00\n        }\n    ],\n    &quot;subtotal&quot;: 609.40\n}\n</code></pre>\n<p>This worked surprisingly well on the first try.\nThe model returned a JSON blob with item names, quantities, and prices.\nSome quantities were slightly off but the structure was correct.</p>\n<p>For zero fine-tuning, I was impressed.</p>\n<h2>Iterating on the System Prompt</h2>\n<p>Here's what I found: when the model misreads a digit or rounds a number, you don't fix a bug in C#.\nYou refine the system prompt.</p>\n<p>What is a system prompt?\nIt's the initial instruction you give the model to set the context for the conversation.\nNotice the <code>ChatRole.System</code> in the code below.\nConversely, we use <code>ChatRole.User</code> for the message where we ask the question.</p>\n<p>After a few rounds of this, my system prompt ended up reading like a specification document:</p>\n<pre><code class=\"language-csharp\">var systemMessage = new ChatMessage(ChatRole.System,\n    @&quot;&quot;&quot;\n    You are a receipt parsing assistant. Extract all line items from the receipt image.\n    For each line item, extract the name, quantity, unit price, and total price.\n    Quantity can be a decimal number (e.g. weight in kg like 0.550 or 1.105).\n    Extract the subtotal which is the final total amount shown on the receipt.\n    IMPORTANT: Read every digit exactly as printed on the receipt.\n    Pay very close attention to each decimal digit - do NOT round or approximate.\n    For example, if the receipt shows 1.105, report exactly 1.105, not 1.1 or 1.2.\n    Verify that quantity * unitPrice = totalPrice for each line item.\n    Don't invent items that aren't on the receipt.\n\n    DECIMAL FORMAT: Receipts may use different number formats depending on locale.\n    - Some use period as decimal separator: 7,499.00\n    - Some use comma as decimal separator: 7.499,00\n    First, detect which format the receipt uses by examining the numbers on it.\n    Then, always output numbers in the JSON using a period as the decimal separator.\n    For example: 7499.00, not 7.499,00 or 7,499.00.\n    &quot;&quot;&quot;);\n</code></pre>\n<p>Every instruction in that prompt exists because the model got something wrong.</p>\n<p>&quot;Read every digit exactly as printed&quot;: it was rounding <code>1.105</code> to <code>1.1</code>.</p>\n<p>&quot;Don't invent items&quot;: it hallucinated a line item that wasn't on the receipt.</p>\n<p>The entire decimal format section: my receipts use commas as decimal separators (European locale),\nand the model kept confusing thousands separators with decimal points.</p>\n<p>Each prompt iteration is like a debugging session with words instead of code.\nIt's not the most fun part, but it's necessary to get accurate results.</p>\n<p>And to think we used to write code to tell computers what to do.\nNow we write prompts to tell models how to think.\nI digress...</p>\n<h2>Strongly Typed Responses</h2>\n<p>This is where <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><code>Microsoft.Extensions.AI</code></a> gets interesting.</p>\n<p>Instead of parsing raw JSON strings yourself, you can call <code>GetResponseAsync&lt;T&gt;</code> and get back a <strong>strongly typed</strong> object:</p>\n<pre><code class=\"language-csharp\">var response = await chatClient.GetResponseAsync&lt;Receipt&gt;(\n    [systemMessage, message],\n    new ChatOptions { Temperature = 0 });\n\nif (response.Result is { } receipt)\n{\n    Console.WriteLine(\n        $&quot;\\nExtracted {receipt.Items.Count} line items:&quot;);\n\n    foreach (var item in receipt.Items)\n    {\n        Console.WriteLine(\n            $&quot;  {item.Name} - &quot; +\n            $&quot;Qty: {item.Quantity} x {item.UnitPrice:C}&quot; +\n            $&quot; = {item.TotalPrice:C}&quot;);\n    }\n\n    Console.WriteLine($&quot;  Subtotal: {receipt.Subtotal:C}&quot;);\n}\n</code></pre>\n<p>The <code>Receipt</code> and <code>LineItem</code> classes are plain C# objects:</p>\n<pre><code class=\"language-csharp\">public class Receipt\n{\n    public List&lt;LineItem&gt; Items { get; set; } = [];\n    public decimal Subtotal { get; set; }\n}\n\npublic class LineItem\n{\n    public string Name { get; set; } = string.Empty;\n    public decimal Quantity { get; set; }\n    public decimal UnitPrice { get; set; }\n    public decimal TotalPrice { get; set; }\n}\n</code></pre>\n<p>The library generates the JSON schema, sends it to the model, and deserializes the response.\nYou get back a <code>Receipt</code> object directly.</p>\n<p>I also set <code>Temperature = 0</code> to make the output as deterministic as possible.\nFor data extraction, you want accuracy.\nThis isn't foolproof, but it helps.</p>\n<p>Here's the object we get back from the model in Visual Studio:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_181/chat_client_typed_response_in_vs.png\" alt=\"Visual Studio screenshot showing the Receipt object with line items and subtotal extracted from the model response.\">\n<h2>Testing Consistency</h2>\n<p>One thing I wanted to verify: if I send the same receipt with the same prompt five times, do I get the same result?</p>\n<pre><code class=\"language-csharp\">const int runs = 5;\nConsole.WriteLine($&quot;\\n--- Consistency test ({runs} runs) ---&quot;);\n\nvar results = new List&lt;Receipt&gt;();\nfor (int i = 0; i &lt; runs; i++)\n{\n    Console.WriteLine($&quot;\\nRun {i + 1}...&quot;);\n    var testResponse = await chatClient.GetResponseAsync&lt;Receipt&gt;(\n        [systemMessage, message],\n        new ChatOptions { Temperature = 0 });\n\n    if (testResponse.Result is { } r)\n    {\n        results.Add(r);\n        Console.WriteLine(\n            $&quot;  Items: {r.Items.Count}, &quot; +\n            $&quot;Subtotal: {r.Subtotal:C}&quot;);\n\n        foreach (var item in r.Items)\n        {\n            Console.WriteLine(\n                $&quot;    {item.Name} - &quot; +\n                $&quot;Qty: {item.Quantity} x {item.UnitPrice:C}&quot; +\n                $&quot; = {item.TotalPrice:C}&quot;);\n        }\n    }\n}\n</code></pre>\n<p>Then I compare every run against the baseline:</p>\n<pre><code class=\"language-csharp\">var baseline = results[0];\nfor (int i = 1; i &lt; results.Count; i++)\n{\n    bool match = baseline.Subtotal == results[i].Subtotal\n        &amp;&amp; baseline.Items.Count == results[i].Items.Count\n        &amp;&amp; baseline.Items.Zip(results[i].Items).All(pair =&gt;\n            pair.First.Name == pair.Second.Name\n            &amp;&amp; pair.First.Quantity == pair.Second.Quantity\n            &amp;&amp; pair.First.UnitPrice == pair.Second.UnitPrice\n            &amp;&amp; pair.First.TotalPrice == pair.Second.TotalPrice);\n\n    Console.WriteLine(\n        $&quot;  Run 1 vs Run {i + 1}: &quot; +\n        $&quot;{(match ? &quot;MATCH&quot; : &quot;DIFFERENT&quot;)}&quot;);\n}\n</code></pre>\n<p>Temperature 0 helps, but vision models aren't perfectly deterministic.\nMost runs matched. Some didn't.\nThe differences were usually small - a misread digit, a slightly different item name.</p>\n<p>This is worth keeping in mind when working with LLMs.\nThey're probabilistic systems.\nEven with temperature 0, the same input can produce slightly different output.\nIf you need guaranteed accuracy, you'll want validation layers on top of this.</p>\n<h2>Where I Want to Take This</h2>\n<p>The receipt scanner is a starting point.\nOnce you have structured data from receipt images, you can build on top of it.</p>\n<p>I've been thinking about extending this into a <strong>personal finance tracker</strong>.\nScan receipts, store the data, and use the same LLM to categorize purchases.\nGroceries, household, electronics - let the model figure it out.</p>\n<p>From there, you could generate <strong>weekly and monthly spending summaries</strong>.\nHow much did I spend on groceries this month?\nHow does that compare to last month?</p>\n<p>You could also do <strong>multi-receipt aggregation</strong> for business trips.\nScan a stack of receipts and generate an expense report.</p>\n<p>Or <strong>price tracking</strong> over time - detect when items at your usual store go up in price.</p>\n<p>We could defintiely build <a href=\"https://milanjovanovic.tech/blog/building-semantic-search-with-amazon-s3-vectors-and-semantic-kernel\"><strong>semantic search</strong></a>\non top of this too.\nSearch through your past receipts for specific items or price ranges.\nThis works by embedding the structured data and using <a href=\"https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide\"><strong>vector search</strong></a>\nto find relevant entries.</p>\n<p>The vision model handles the hard part: turning unstructured images into structured data.\nEverything after that is regular application development.</p>\n<p>I might build some of this out. We'll see.</p>\n<h2>Summary</h2>\n<p>Running a vision model locally with Ollama is straightforward to set up.\n<code>Microsoft.Extensions.AI</code> and <code>OllamaSharp</code> make the .NET integration clean.\nYou get a provider-agnostic <code>IChatClient</code> with support for strongly typed responses.\nYou could also run this with <a href=\"https://milanjovanovic.tech/blog/building-generative-ai-applications-with-github-models-and-dotnet-aspire\"><strong>Aspire and GitHub models</strong></a>\nif you want to keep everything in the Microsoft ecosystem.</p>\n<p>The system prompt is where most of the work happens.\nEvery line in mine was the result of the model getting something wrong and me adding a corrective instruction.</p>\n<p>If you want to try this yourself:</p>\n<ol>\n<li>Install <a href=\"https://ollama.com/\">Ollama</a></li>\n<li>Pull the vision model: <code>ollama pull llama3.2-vision:latest</code></li>\n<li>Create a .NET console app and add the <code>OllamaSharp</code> and <code>Microsoft.Extensions.AI</code> NuGet packages</li>\n<li>Point it at a receipt and see what comes back</li>\n</ol>\n<p>Hope this was useful. See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-extract-structured-data-from-images-using-ollama-in-dotnet",
            "title": "How to Extract Structured Data From Images Using Ollama in .NET",
            "summary": "Vision models can do more than describe images. I used Ollama and Microsoft.Extensions.AI to extract structured receipt data into strongly typed C# objects…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_181.png",
            "date_modified": "2026-02-14T00:00:00.000Z",
            "date_published": "2026-02-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/integrate-keycloak-with-aspnetcore-using-oauth-2",
            "content_html": "<p>You can hand authentication to Keycloak instead of building it: run it as a container, create a realm with a public client, and point your .NET API at its metadata endpoint.\nSwagger UI drives the OAuth 2.0 Authorization Code flow with PKCE, and the API validates the resulting JWT locally against Keycloak's cached signing keys.</p>\n<p><strong>Authentication</strong> is one of those things that's easy to get wrong and <strong>expensive to fix later</strong>.\nRolling your own auth system means dealing with password hashing, token management, session handling,\nand a never-ending stream of security patches.</p>\n<p>I was never a fan of this...</p>\n<p>What if you could outsource all of that to a battle-tested <strong>identity provider</strong>?</p>\n<p><a href=\"https://www.keycloak.org/\">Keycloak</a> is an open-source identity and access management solution.\nIt handles user authentication, authorization, and identity brokering (social logins, enterprise SSO) out of the box.\nYou get a polished admin console, built-in support for <strong>OAuth 2.0</strong> and <strong>OpenID Connect</strong>, and it runs anywhere Docker does.</p>\n<p>We'll spin up Keycloak as a container, create a realm with a public client,\nand wire up Swagger UI to authenticate using the <a href=\"https://oauth.net/2/\">OAuth 2.0</a> <strong>Authorization Code flow</strong>.\nThen we'll add <a href=\"https://www.rfc-editor.org/rfc/rfc7519.html\">JWT</a> validation to our .NET backend and\ntrace the entire authentication flow using the <a href=\"https://milanjovanovic.tech/blog/standalone-aspire-dashboard-setup-for-distributed-dotnet-applications\"><strong>Aspire Dashboard</strong></a>.</p>\n<h2>Running Keycloak as a Container</h2>\n<p>The fastest way to spin up <strong>Keycloak</strong> is with Docker&gt;).\nWe'll run it in development mode, which disables HTTPS and uses an embedded H2 database.\nThis is perfect for local development but <strong>not suitable for production</strong> (more on that later).</p>\n<p>Here's a minimal <code>docker-compose.yml</code>:</p>\n<pre><code class=\"language-yaml\">services:\n  keycloak:\n    image: quay.io/keycloak/keycloak:26.5.2\n    container_name: keycloak\n    environment:\n      - KC_BOOTSTRAP_ADMIN_USERNAME=admin\n      - KC_BOOTSTRAP_ADMIN_PASSWORD=admin\n    ports:\n      - '8080:8080'\n    command: start-dev\n</code></pre>\n<p>Start it with:</p>\n<pre><code class=\"language-bash\">docker compose up -d\n</code></pre>\n<p>Once Keycloak is running, navigate to <code>http://localhost:8080</code> and log in with <code>admin</code> / <code>admin</code>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_admin_login.png\" alt=\"The Keycloak admin login screen with username and password fields.\">\n<p>You should see the Keycloak admin console.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_admin_console.png\" alt=\"The Keycloak admin console showing the master realm dashboard.\">\n<h2>Setting Up a Realm and Client</h2>\n<p>Keycloak organizes everything into <strong>realms</strong>.\nA realm is a space where you manage users, roles, and applications.\nThe <code>master</code> realm is reserved for Keycloak administration, so we'll create a new one for our application.</p>\n<h3>Creating a Realm</h3>\n<ol>\n<li>Click the <strong>Manage Realms</strong> button in the top-left corner</li>\n<li>Click <strong>Create realm</strong></li>\n<li>Enter a name (e.g., <code>keycloak-demo</code>) and click <strong>Create</strong></li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_create_realm_dialog.png\" alt=\"The Keycloak create realm dialog with \">\n<h3>Creating a Public Client</h3>\n<p>Now we need to register our application.\nIn OAuth 2.0 terms, this is a <strong>client</strong>.\nSince Swagger UI runs in the browser, we'll create a <strong>public client</strong> (no client secret).</p>\n<ol>\n<li>Go to <strong>Clients</strong> → <strong>Create client</strong></li>\n<li>Set <strong>Client ID</strong> to <code>demo-api</code></li>\n<li>Leave <strong>Client type</strong> as <code>OpenID Connect</code></li>\n<li>Click <strong>Next</strong></li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_create_client_step1.png\" alt=\"The first step of creating a client in Keycloak, showing the Client ID field.\">\n<ol start=\"5\">\n<li>Enable <strong>Client authentication</strong>: Off (public client)</li>\n<li>Check <strong>Standard flow</strong> (Authorization Code)</li>\n<li>Choose <strong>PKCE Method</strong>: S256 (SHA-256)</li>\n<li>Click <strong>Next</strong></li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_create_client_step2.png\" alt=\"The second step of creating a client showing authentication settings.\">\n<ol start=\"9\">\n<li>Configure the redirect URIs:\n<ul>\n<li><strong>Valid redirect URIs</strong>: <code>https://localhost:5001/*</code> (your API's Swagger URL)</li>\n<li><strong>Web origins</strong>: <code>https://localhost:5001</code></li>\n</ul>\n</li>\n<li>Click <strong>Save</strong></li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_create_client_step3.png\" alt=\"The third step showing redirect URI configuration.\">\n<h3>Creating a Test User</h3>\n<p>We need a user to authenticate with.</p>\n<ol>\n<li>Go to <strong>Users</strong> → <strong>Add user</strong></li>\n<li>Fill in the details (username, email, etc.)</li>\n<li>Leave <strong>Email Verified</strong> checked to avoid email confirmation</li>\n<li>Click <strong>Create</strong></li>\n<li>Go to the <strong>Credentials</strong> tab</li>\n<li>Click <strong>Set password</strong> and create a password (disable &quot;Temporary&quot;)</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/keycloak_create_user.png\" alt=\"The Keycloak user creation form.\">\n<p>You're now ready to authenticate users against Keycloak!</p>\n<h2>The Authorization Code Flow</h2>\n<p>Before we dive into code, let's understand what happens when a user authenticates.\nThe <a href=\"https://www.rfc-editor.org/rfc/rfc6749#section-4.1\"><strong>Authorization Code flow</strong></a> is the recommended OAuth 2.0 flow for browser-based applications.</p>\n<p>There's an important security enhancement called <strong>PKCE</strong> (<a href=\"https://www.rfc-editor.org/rfc/rfc7636\">Proof Key for Code Exchange</a>)\nthat prevents authorization code interception attacks.\nIt works by having the client generate a random secret (the code verifier) and deriving a hash (the code challenge) sent in the initial authorization request.\nWhen exchanging the authorization code for tokens, the client must present the original code verifier.</p>\n<p>Here's the sequence:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/authorization_code_flow.png\" alt=\"A sequence diagram showing the OAuth 2.0 Authorization Code flow between Browser, API, and Keycloak.\">\n<ol>\n<li><strong>User clicks &quot;Authorize&quot;</strong> in Swagger UI</li>\n<li><strong>Browser redirects</strong> to Keycloak's authorization endpoint</li>\n<li><strong>User logs in</strong> at Keycloak</li>\n<li><strong>Keycloak redirects back</strong> with an authorization code</li>\n<li><strong>Swagger UI exchanges</strong> the code for tokens (access token, refresh token, ID token)</li>\n<li><strong>Swagger UI attaches</strong> the access token to API requests</li>\n<li><strong>API validates</strong> the token signature and claims</li>\n</ol>\n<p>The beauty of this flow is that credentials never touch your application.\nThe user authenticates directly with Keycloak, and your API only sees signed tokens.</p>\n<h2>Configuring Swagger UI with OAuth 2.0</h2>\n<p>Now let's set up our .NET API to use Swagger UI as our OAuth 2.0 test client.</p>\n<p>First, install the required packages:</p>\n<pre><code class=\"language-bash\">dotnet add package Swashbuckle.AspNetCore\n</code></pre>\n<p>Configure Swagger in your <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">var keycloakAuthority = builder.Configuration[&quot;Keycloak:Authority&quot;]!;\nvar keycloakClientId = builder.Configuration[&quot;Keycloak:ClientId&quot;]!;\n\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen(options =&gt;\n{\n    options.SwaggerDoc(&quot;v1&quot;, new OpenApiInfo\n    {\n        Title = &quot;Demo API&quot;,\n        Version = &quot;v1&quot;\n    });\n\n    // Define the OAuth 2.0 security scheme\n    options.AddSecurityDefinition(nameof(SecuritySchemeType.OAuth2), new OpenApiSecurityScheme\n    {\n        Type = SecuritySchemeType.OAuth2,\n        Flows = new OpenApiOAuthFlows\n        {\n            AuthorizationCode = new OpenApiOAuthFlow\n            {\n                AuthorizationUrl = new Uri($&quot;{keycloakAuthority}/protocol/openid-connect/auth&quot;),\n                TokenUrl = new Uri($&quot;{keycloakAuthority}/protocol/openid-connect/token&quot;),\n                Scopes = new Dictionary&lt;string, string&gt;\n                {\n                    { &quot;openid&quot;, &quot;OpenID Connect scope&quot; },\n                    { &quot;profile&quot;, &quot;User profile&quot; }\n                }\n            }\n        }\n    });\n\n    // Apply security to all operations\n    options.AddSecurityRequirement(doc =&gt; new OpenApiSecurityRequirement\n    {\n        {\n            new OpenApiSecuritySchemeReference(nameof(SecuritySchemeType.OAuth2), doc),\n            []\n        }\n    });\n});\n</code></pre>\n<p>And configure the Swagger UI middleware:</p>\n<pre><code class=\"language-csharp\">if (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI(options =&gt;\n    {\n        options.OAuthClientId(keycloakClientId); // Default Client ID\n        options.OAuthUsePkce(); // Proof Key for Code Exchange (security enhancement)\n    });\n}\n</code></pre>\n<p>Your <code>appsettings.Development.json</code>:</p>\n<pre><code class=\"language-json\">{\n  &quot;Keycloak&quot;: {\n    &quot;Authority&quot;: &quot;http://localhost:8080/realms/keycloak-demo&quot;,\n    &quot;ClientId&quot;: &quot;demo-api&quot;,\n    &quot;Audience&quot;: &quot;account&quot;,\n    &quot;Issuer&quot;: &quot;http://localhost:8080/realms/keycloak-demo&quot;,\n    // Here we use the Docker service name for Keycloak\n    &quot;MetadataAddress&quot;: &quot;http://keycloak:8080/realms/keycloak-demo/.well-known/openid-configuration&quot;\n  }\n}\n</code></pre>\n<p>Now when you open Swagger UI, you'll see an <strong>Authorize</strong> button.\nClicking it opens the OAuth flow, redirecting you to Keycloak to log in.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/swagger_authorize_form.png\" alt=\"Swagger UI showing the Authorize form for OAuth 2.0.\">\n<h2>Adding JWT Validation</h2>\n<p>At this point, Swagger UI can obtain tokens, but our API isn't validating them yet.\nLet's add <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT Bearer authentication</strong></a>.</p>\n<p>Install the authentication package:</p>\n<pre><code class=\"language-bash\">dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer\n</code></pre>\n<p>Configure authentication in <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =&gt;\n    {\n        options.MetadataAddress = builder.Configuration[&quot;Keycloak:MetadataAddress&quot;]!;\n        options.Audience = builder.Configuration[&quot;Keycloak:Audience&quot;];\n\n        options.TokenValidationParameters = new TokenValidationParameters\n        {\n            ValidIssuer = builder.Configuration[&quot;Keycloak:Issuer&quot;]\n        };\n\n        // Required for HTTP in development (Keycloak uses HTTP by default in dev mode)\n        options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();\n    });\n\nbuilder.Services.AddAuthorization();\n</code></pre>\n<p>The default <code>TokenValidationParameters</code> will validate the token signature, expiration, issuer, and audience.</p>\n<p>Add the middleware:</p>\n<pre><code class=\"language-csharp\">app.UseAuthentication();\napp.UseAuthorization();\n</code></pre>\n<p>Create a protected endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;users/me&quot;, (ClaimsPrincipal user) =&gt;\n{\n    return Results.Ok(new\n    {\n        UserId = user.FindFirstValue(ClaimTypes.NameIdentifier),\n        Email = user.FindFirstValue(ClaimTypes.Email),\n        Name = user.FindFirstValue(&quot;preferred_username&quot;),\n        Claims = user.Claims.Select(c =&gt; new { c.Type, c.Value })\n    });\n})\n.RequireAuthorization();\n</code></pre>\n<h2>How JWT Validation Works</h2>\n<p>When a request hits your protected endpoint, here's what happens under the hood:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/jwt_validation_flow.png\" alt=\"A sequence diagram showing how JWT validation works in ASP.NET Core.\">\n<ol>\n<li><strong>Middleware extracts</strong> the <code>Authorization: Bearer &lt;token&gt;</code> header</li>\n<li><strong>JWT Handler fetches</strong> Keycloak's public keys from the JWKS endpoint (cached)</li>\n<li><strong>Signature validation</strong> proves the token wasn't tampered with</li>\n<li><strong>Claims are extracted</strong> and the <code>ClaimsPrincipal</code> is populated</li>\n<li><strong>Authorization middleware</strong> checks if the user meets the endpoint requirements</li>\n<li><strong>Endpoint executes</strong> with access to <code>HttpContext.User</code></li>\n</ol>\n<p>The key insight here is that your API <strong>never contacts Keycloak</strong> to validate individual tokens.\nIt fetches the signing keys once and validates tokens locally.\nThis is what makes JWT-based authentication so fast.</p>\n<h2>Observing the Flow with Aspire Dashboard</h2>\n<p>If you're using <a href=\"https://aspire.dev\"><strong>Aspire</strong></a>, you can observe the entire authentication flow in the distributed traces.</p>\n<p>Here's what a successful authentication looks like:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_180/aspire_auth_trace.png\" alt=\"Aspire Dashboard showing a distributed trace of the authentication flow.\">\n<p>You can see:</p>\n<ol>\n<li>The initial request to <code>users/me</code> (with the Bearer token)</li>\n<li>The outbound call to Keycloak's <code>.well-known/openid-configuration</code> endpoint</li>\n<li>The outbound call to Keycloak's JWKS endpoint (fetching signing keys)</li>\n<li>The response back to the client</li>\n</ol>\n<p>On subsequent requests, you won't see the JWKS call because the keys are cached.</p>\n<p>This is why JWT validation adds virtually no latency after the initial key fetch.</p>\n<h2>Production Considerations</h2>\n<p>What we've built is great for development.\nFor production, you'll want to address a few things:</p>\n<p><strong>1. HTTPS Everywhere</strong></p>\n<p>Keycloak should run behind HTTPS.\nSet <code>KC_HOSTNAME</code> and configure TLS certificates.</p>\n<p><strong>2. Persistent Storage</strong></p>\n<p>Replace the embedded H2 database with PostgreSQL or MySQL:</p>\n<pre><code class=\"language-yaml\">environment:\n  - KC_DB=postgres\n  - KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak\n  - KC_DB_USERNAME=keycloak\n  - KC_DB_PASSWORD=secret\n</code></pre>\n<p><strong>3. Require HTTPS Metadata</strong></p>\n<p>Remove <code>options.RequireHttpsMetadata = false</code> in production.</p>\n<h2>Summary</h2>\n<p>In about 10 minutes, we've set up:</p>\n<ul>\n<li>A <a href=\"https://milanjovanovic.tech/blog/containerize-your-dotnet-applications-without-a-dockerfile\"><strong>containerized</strong></a> Keycloak instance</li>\n<li>A realm with a public OAuth 2.0 client</li>\n<li>Swagger UI acting as an OAuth client with Authorization Code + PKCE</li>\n<li>JWT validation in ASP.NET Core</li>\n<li>Observability with <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>OpenTelemetry</strong></a> into the authentication flow</li>\n</ul>\n<p>What I really like about Keycloak is how easy it is to extend.\nWant Google login? Configure it in Keycloak.\nNeed enterprise SSO? Add a SAML provider.\nYour API code stays exactly the same because it just validates tokens.</p>\n<p>If you want to see how I integrate Keycloak in a real-world system with role-based access control,\ncheck out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> and\n<a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/integrate-keycloak-with-aspnetcore-using-oauth-2",
            "title": "Integrate Keycloak with ASP.NET Core Using OAuth 2.0",
            "summary": "Learn how to integrate Keycloak with your .NET 10 API using Docker, Swagger UI with OAuth 2.0 Authorization Code flow, and JWT validation.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_180.png",
            "date_modified": "2026-02-07T00:00:00.000Z",
            "date_published": "2026-02-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/containerize-your-dotnet-applications-without-a-dockerfile",
            "content_html": "<p>Since .NET 7, the SDK can publish an application straight to a container image, so you don't need a Dockerfile.\nRun <code>dotnet publish --os linux --arch x64 /t:PublishContainer</code> and the SDK builds the app, picks a base image, and loads it into Docker.\nYou customize the image through MSBuild properties in the <code>.csproj</code>.</p>\n<p>Containers have become the standard for deploying modern applications.\nBut if you've ever written a <a href=\"https://docs.docker.com/reference/dockerfile/\">Dockerfile</a>, you know it can be tedious.\nYou need to understand <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>multi-stage builds</strong></a>, pick the right base images, configure the right ports, and remember to copy files in the correct order.</p>\n<p>What if I told you that <strong>you don't need a Dockerfile at all</strong>?</p>\n<p>Since .NET 7, the SDK has built-in support for publishing your application directly to a container image.\nYou can do this with a single <code>dotnet publish</code> command.</p>\n<p>In this week's newsletter, we'll explore:</p>\n<ul>\n<li>Why Dockerfile-less publishing matters</li>\n<li>How to enable container publishing in your project</li>\n<li>Customizing the container image</li>\n<li>Publishing to container registries</li>\n<li><strong>How I'm using this to deploy to a VPS</strong></li>\n</ul>\n<h2>The Traditional Approach: Writing a Dockerfile</h2>\n<p>Before we look at the SDK approach, let's see what we're replacing.</p>\n<p>A typical multi-stage Dockerfile for a .NET application looks like this:</p>\n<pre><code class=\"language-bash\">FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base\nWORKDIR /app\nEXPOSE 8080\nEXPOSE 8081\n\nFROM mcr.microsoft.com/dotnet/sdk:10.0 AS build\nARG BUILD_CONFIGURATION=Release\nWORKDIR /src\nCOPY [&quot;src/MyApi/MyApi.csproj&quot;, &quot;src/MyApi/&quot;]\nRUN dotnet restore &quot;src/MyApi/MyApi.csproj&quot;\n\nCOPY . .\nWORKDIR &quot;/src/src/MyApi&quot;\nRUN dotnet build &quot;MyApi.csproj&quot; -c $BUILD_CONFIGURATION  -o /app/build\n\nFROM build AS publish\nARG BUILD_CONFIGURATION=Release\nRUN dotnet publish &quot;MyApi.csproj&quot; -c $BUILD_CONFIGURATION -o /app/publish\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nENTRYPOINT [&quot;dotnet&quot;, &quot;MyApi.dll&quot;]\n</code></pre>\n<p>This works, but there's a learning curve and maintenance overhead:</p>\n<ul>\n<li><strong>Maintenance burden</strong>: You need to update base image tags manually</li>\n<li><strong>Layer caching</strong>: Getting the COPY order wrong kills your build cache</li>\n<li><strong>Duplication</strong>: Every project needs a similar Dockerfile</li>\n<li><strong>Context switching</strong>: You're writing Docker DSL, not .NET code</li>\n</ul>\n<p>The .NET SDK approach eliminates all of this.</p>\n<h2>Enabling Container Publishing</h2>\n<p>If you're running on .NET 10, you don't need to do anything special to enable container publishing.\nThis will work for ASP.NET Core apps, worker services, and console apps.</p>\n<p>You can publish directly to a container image:</p>\n<pre><code class=\"language-bash\">dotnet publish --os linux --arch x64 /t:PublishContainer\n</code></pre>\n<p>That's it. The .NET SDK will:</p>\n<ol>\n<li>Build your application</li>\n<li>Select the appropriate base image</li>\n<li>Create a container image with your published output</li>\n<li>Load it into your local OCI-compliant daemon</li>\n</ol>\n<p>The most popular option is Docker, but it also works with Podman.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_179/dotnet_publish_container.png\" alt=\"An image showing the output of the dotnet publish command creating a container image.\">\n<h2>Customizing the Container Image</h2>\n<p>The SDK provides sensible defaults, but you'll often want to customize the image.\nFor a more comprehensive list of options, see the <a href=\"https://learn.microsoft.com/en-us/dotnet/core/containers/publish-configuration\">official docs</a>.</p>\n<p>I'll cover the most common customizations here.</p>\n<h3>Setting the Image Name and Tag</h3>\n<p>The <code>ContainerRepository</code> property sets the image name (repository).\nThe <code>ContainerImageTags</code> property sets one or more tags (separated by semicolons).\nIf you want a single tag, you can use <code>ContainerImageTag</code> instead.</p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\n  &lt;ContainerRepository&gt;ghcr.io/USERNAME/REPOSITORY&lt;/ContainerRepository&gt;\n  &lt;ContainerImageTags&gt;1.0.0;latest&lt;/ContainerImageTags&gt;\n&lt;/PropertyGroup&gt;\n</code></pre>\n<p>From .NET 8 and onwards, when a tag isn't provided the default is <code>latest</code>.</p>\n<h3>Choosing a Different Base Image</h3>\n<p>By default, the SDK uses the following base images:</p>\n<ul>\n<li><code>mcr.microsoft.com/dotnet/runtime-deps</code> for self-contained apps</li>\n<li><code>mcr.microsoft.com/dotnet/aspnet</code> image for ASP.NET Core apps</li>\n<li><code>mcr.microsoft.com/dotnet/runtime</code> for other cases</li>\n</ul>\n<p>You can switch to a smaller or different image:</p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\n  &lt;!-- Use the Alpine-based image for smaller size --&gt;\n  &lt;ContainerBaseImage&gt;mcr.microsoft.com/dotnet/aspnet:10.0-alpine&lt;/ContainerBaseImage&gt;\n&lt;/PropertyGroup&gt;\n</code></pre>\n<p>You could also do this by setting <code>ContainerFamily</code> to <code>alpine</code>, and letting the rest be inferred.</p>\n<p>Here's the size difference between the default and Alpine images for an ASP.NET Core app:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_179/container_image_size.png\" alt=\"An image showing the size difference between the default and Alpine base images for ASP.NET Core applications.\">\n<pre><code class=\"language-text\">| Base Image                                  | Size (MB) |\n| ------------------------------------------- | --------- |\n| mcr.microsoft.com/dotnet/aspnet:10.0        | 231.73    |\n| mcr.microsoft.com/dotnet/aspnet:10.0-alpine | 122.65    |\n</code></pre>\n<p>You can see a significant size reduction by switching to <code>alpine</code>.</p>\n<h3>Configuring Ports</h3>\n<p>For web applications, the default exposed ports are <code>8080</code> and <code>8081</code> for HTTP and HTTPS.\nThese are inferred from ASP.NET Core environment variables (<code>ASPNETCORE_URLS</code>, <code>ASPNETCORE_HTTP_PORT</code>, <code>ASPNETCORE_HTTPS_PORT</code>).\nThe <code>Type</code> attribute can be <code>tcp</code> or <code>udp</code>.</p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;ContainerPort Include=&quot;8080&quot; Type=&quot;tcp&quot; /&gt;\n  &lt;ContainerPort Include=&quot;8081&quot; Type=&quot;tcp&quot; /&gt;\n&lt;/ItemGroup&gt;\n</code></pre>\n<h2>Publishing to a Container Registry</h2>\n<p>Publishing locally is useful for development, but you'll want to push to a registry for deployment.\nYou can specify the target registry during publishing.</p>\n<p>Here's an example publishing to GitHub Container Registry:</p>\n<pre><code class=\"language-bash\">dotnet publish --os linux --arch x64  /t:PublishContainer /p:ContainerRegistry=ghcr.io\n</code></pre>\n<p><strong>Authentication</strong>: The SDK uses your local Docker credentials.\nMake sure you've logged in with <code>docker login</code> before publishing to a remote registry.</p>\n<p>However, <strong>I don't use the above approach</strong>.\nI prefer using docker CLI for the publishing step, as it gives me more control over authentication and tagging.</p>\n<h2>CI/CD Integration</h2>\n<p>Here's what I'm doing in my <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>GitHub Actions workflow</strong></a> to build and push my .NET app container.\nI left out the boring bits of seting up the .NET environment and checking out code.</p>\n<p>This will build the container image, tag it, and push it to <a href=\"https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry\">GitHub Container Registry</a>:</p>\n<pre><code class=\"language-yaml\">- name: Publish\n  run: dotnet publish &quot;${{ env.WORKING_DIRECTORY }}&quot; --configuration ${{ env.CONFIGURATION }} --os linux -t:PublishContainer\n# Tag the build for later steps\n- name: Log in to ghcr.io\n  run: echo &quot;${{ env.DOCKER_PASSWORD }}&quot; | docker login ghcr.io -u &quot;${{ env.DOCKER_USERNAME }}&quot; --password-stdin\n- name: Tag Docker image\n  run:\n    docker tag ${{ env.IMAGE_NAME }}:${{ github.sha }} ghcr.io/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ github.sha }} |\n    docker tag ${{ env.IMAGE_NAME }}:latest ghcr.io/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest\n- name: Push Docker image\n  run:\n    docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ github.sha }} |\n    docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest\n</code></pre>\n<p>Once my images are up in the registry, I can deploy them to my VPS.</p>\n<p>I'm using <a href=\"https://dokploy.com/\">Dokploy</a> (a simple but powerful deployment tool for Docker apps) to pull the latest image and restart my service.</p>\n<pre><code class=\"language-yaml\">deploy:\n  runs-on: ubuntu-latest\n  needs: build-and-publish\n  steps:\n    - name: Trigger deployment\n      run: |\n        curl -X POST ${{ env.DEPLOYMENT_TRIGGER_URL }} \\\n          -H 'accept: application/json' \\\n          -H 'Content-Type: application/json' \\\n          -H 'x-api-key: ${{ env.DEPLOYMENT_TRIGGER_API_KEY }}' \\\n          -d '{\n            &quot;applicationId&quot;: &quot;${{ env.DEPLOYMENT_TRIGGER_APP_ID }}&quot;\n          }'\n</code></pre>\n<p>This kicks off a deployment on my VPS, pulling the latest image and restarting the container.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_179/dokploy_deployment.png\" alt=\"An image showing the output of the dokploy deployment command restarting the container.\">\n<p>By the way, I'm running my VPS on <a href=\"https://www.hetzner.com/cloud\">Hetzner Cloud</a> - highly recommended if you're looking for affordable and reliable VPS hosting.</p>\n<h2>When You Still Need a Dockerfile</h2>\n<p>The SDK container support is powerful, but it doesn't cover every scenario.</p>\n<p>You'll still need a Dockerfile when:</p>\n<ul>\n<li><strong>Installing system dependencies</strong>: If your app needs native libraries (like <code>libgdiplus</code> for image processing)</li>\n<li><strong>Complex multi-stage builds</strong>: When you need to run custom build steps</li>\n<li><strong>Non-.NET components</strong>: If your container needs additional services or tools</li>\n</ul>\n<p>For most web APIs and background services, the SDK approach is sufficient.</p>\n<h2>Summary</h2>\n<p>The .NET SDK's built-in container support removes the friction of containerization.</p>\n<p>You get:</p>\n<ul>\n<li><strong>No Dockerfile to maintain</strong> - one less file to worry about</li>\n<li><strong>Automatic base image selection</strong> - always uses the right image for your framework version</li>\n<li><strong>MSBuild integration</strong> - configure everything in your <code>.csproj</code></li>\n<li><strong>CI/CD friendly</strong> - works anywhere <code>dotnet</code> runs</li>\n</ul>\n<p>The days of copy-pasting Dockerfiles between projects are over.</p>\n<p>Just enable the feature, customize what you need, and publish.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/containerize-your-dotnet-applications-without-a-dockerfile",
            "title": "Containerize Your .NET Applications Without a Dockerfile",
            "summary": "Learn how to containerize .NET applications without writing a single line of Dockerfile. The .NET SDK has built-in support for publishing directly to container…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_179.png",
            "date_modified": "2026-01-31T00:00:00.000Z",
            "date_published": "2026-01-31T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/a-practical-demo-of-zero-downtime-migrations-using-password-hashing",
            "content_html": "<p>Password hashing is one-way, so you cannot upgrade stored hashes in place.\nThe zero-downtime approach is migration on login: verify with the new algorithm first, fall back to the legacy one, and re-hash the password with the new algorithm as soon as the legacy check succeeds.</p>\n<p>Security requirements evolve.\nWhat was considered &quot;secure enough&quot; five years ago might not pass a security audit today.</p>\n<p>You need to upgrade to a modern algorithm like <a href=\"https://en.wikipedia.org/wiki/Argon2\">Argon2</a> or <a href=\"https://en.wikipedia.org/wiki/Bcrypt\">Bcrypt</a>.\nBut here is the problem: hashing is a <strong>one-way operation</strong>.\nYou cannot reverse-engineer the existing hashes to &quot;upgrade&quot; them.</p>\n<p>If you simply swap your <code>IPasswordHasher</code> implementation, you break the application.\nEvery single existing user who tries to log in will fail authentication because your new hasher doesn't understand the old format.</p>\n<p>In this article, I want to demo a <strong>zero-downtime migration</strong> concept in practice.</p>\n<p>Real systems have more constraints (and you should not build auth from scratch).\nBut this is a clean example of a <strong>pattern</strong> you can reuse for <a href=\"https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide\"><strong>database migrations</strong></a>:</p>\n<ul>\n<li>Move from old format to new format</li>\n<li>Keep existing behavior working</li>\n<li>Gradually migrate data</li>\n<li>Delete legacy only when you are done</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Naive Approach and Why It Fails</h2>\n<p>Let's imagine you have a simple authentication system.\nYou want to replace your legacy <code>PBKDF2</code> hasher with a standard <code>Argon2</code> implementation.</p>\n<p>You might think, &quot;I'll just register the new implementation in the dependency injection container.&quot;</p>\n<pre><code class=\"language-csharp\">// Switching from LegacyHasher to ModernHasher\nbuilder.Services.AddSingleton&lt;IPasswordHasher, ModernHasher&gt;();\n</code></pre>\n<p>Here is the failure scenario:</p>\n<ol>\n<li><strong>New Users:</strong> They register and log in perfectly.\nTheir passwords are hashed with Argon2 from day one.</li>\n<li><strong>Existing Users:</strong> A user enters their correct password.\nThe system fetches the <em>old</em> <a href=\"https://en.wikipedia.org/wiki/PBKDF2\">PBKDF2</a> hash from the database.</li>\n<li><strong>The Crash:</strong> The <code>ModernHasher</code> tries to verify the PBKDF2 hash.\nIt fails immediately, returning <code>401 Unauthorized</code>.</li>\n</ol>\n<p>You have inadvertently locked out your entire user base.\nWe need a way to support <em>both</em> algorithms simultaneously without making the login code a mess.</p>\n<h2>The Solution: Migration on Login</h2>\n<p>The strategy is simple: we don't migrate the database in a batch job.\nWe migrate users lazily when they prove their identity.</p>\n<p>The flow looks like this:</p>\n<ol>\n<li><strong>Attempt 1:</strong> Try to verify the password using the <strong>New</strong> algorithm.</li>\n<li><strong>Attempt 2 (Fallback):</strong> If that fails, check if the <strong>Legacy</strong> algorithm can verify it.</li>\n<li><strong>The Migration:</strong> If the <em>Legacy</em> verification succeeds:\n<ul>\n<li>Log the user in (Success).</li>\n<li><strong>Immediately re-hash</strong> their password using the <strong>New</strong> algorithm.</li>\n<li>Update the database record.</li>\n</ul>\n</li>\n</ol>\n<p>Future logins for this user will now succeed via the standard flow.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_178/migration_on_login_flow.png\" alt=\"A sequence diagram showing the migration on login flow, with attempts to verify using new and legacy hashers.\">\n<h2>Implementation with .NET Keyed Services</h2>\n<p>In .NET 8, Microsoft introduced <strong>Keyed Services</strong>, which are perfect for this scenario.\nThey allow us to register multiple implementations of the same interface and retrieve them by name.</p>\n<h3>1. Registering the Services</h3>\n<p>We register both hashers in our <code>Program.cs</code>, assigning them unique keys:</p>\n<pre><code class=\"language-csharp\">// Register the implementations with specific keys\nbuilder.Services.AddKeyedSingleton&lt;IPasswordHasher, Pbdkf2PasswordHasher&gt;(&quot;legacy&quot;);\nbuilder.Services.AddKeyedSingleton&lt;IPasswordHasher, Argon2PasswordHasher&gt;(&quot;modern&quot;);\n\n// (Optional) Register the modern one as the default for other services\nbuilder.Services.AddSingleton&lt;IPasswordHasher, Argon2PasswordHasher&gt;();\n</code></pre>\n<h3>2. The Login Command Handler</h3>\n<p>Now we implement the migration logic.\nWe inject both hashers using the <code>[FromKeyedServices]</code> attribute.</p>\n<pre><code class=\"language-csharp\">public class LoginCommandHandler(\n    IUserRepository userRepository,\n    [FromKeyedServices(&quot;modern&quot;)] IPasswordHasher newHasher,\n    [FromKeyedServices(&quot;legacy&quot;)] IPasswordHasher legacyHasher)\n{\n    public async Task&lt;AuthenticationResult&gt; Handle(LoginCommand command)\n    {\n        var user = await userRepository.GetByEmailAsync(command.Email);\n        if (user is null)\n        {\n            return AuthenticationResult.Fail();\n        }\n\n        // 1. Try the new algorithm first (Happy Path)\n        if (newHasher.Verify(user.PasswordHash, command.Password))\n        {\n            return AuthenticationResult.Success(user);\n        }\n\n        // 2. Fallback: Check if it's a legacy hash\n        if (legacyHasher.Verify(user.PasswordHash, command.Password))\n        {\n            // 3. MIGRATION STEP: Re-hash and save\n            var newHash = newHasher.Hash(command.Password);\n\n            user.UpdatePasswordHash(newHash);\n            await userRepository.SaveChangesAsync();\n\n            return AuthenticationResult.Success(user);\n        }\n\n        return AuthenticationResult.Fail();\n    }\n}\n\n</code></pre>\n<p>This code ensures that active users are automatically upgraded.\nAfter a few months, the vast majority of your user base will be on the new algorithm.</p>\n<h2>Real-World Improvements</h2>\n<p>While the implementation above works, here are two improvements to make it production-ready.</p>\n<h3>1. Algorithm Prefixes</h3>\n<p>Relying on &quot;trial and error&quot; verification works, but it's cleaner to know exactly which algorithm was used to create a hash.</p>\n<p>Standard algorithms often include a prefix (e.g., Bcrypt starts with <code>$2a$</code> or <code>$2b$</code>).\nYou can use this to route the request efficiently:</p>\n<pre><code class=\"language-csharp\">public bool IsLegacyHash(string hash)\n{\n    // This assumes we're storing a prefix for PBKDF2 hashes. Something to consider.\n    return hash.StartsWith(&quot;pbkdf2$&quot;);\n}\n</code></pre>\n<p>Another benefit this unlocks is being able to query the database for users still on the legacy format.</p>\n<h3>2. Feature Flags</h3>\n<p>Performing a database write during a login request adds latency.\nIf you have high traffic, you might want to control this roll-out.</p>\n<p>By wrapping the migration logic behind a <a href=\"https://milanjovanovic.tech/blog/feature-flags-in-dotnet-and-how-i-use-them-for-ab-testing\"><strong>Feature Flag</strong></a>, you can disable the &quot;write&quot; step if your database comes under load,\nwhile still allowing users to log in via the read-only fallback.</p>\n<pre><code class=\"language-csharp\">if (await featureManager.IsEnabledAsync(FeatureFlags.MigratePasswords) &amp;&amp;\n    legacyHasher.Verify(user.PasswordHash, command.Password))\n{\n    // Perform migration...\n}\n</code></pre>\n<h2>Finishing the Migration</h2>\n<p>After you run this for a while (usually a few months), most active accounts will be upgraded.\nYou can then run a cleanup script to identify any remaining legacy hashes and force those users to reset their passwords on the next login attempt.</p>\n<p>At that point, you can remove:</p>\n<ul>\n<li>The legacy hasher registration</li>\n<li>The legacy verification code path</li>\n<li>The feature flag</li>\n</ul>\n<p>And the migration is complete.</p>\n<h2>Summary</h2>\n<p>A &quot;simple&quot; hashing upgrade is really a <strong>data migration</strong>.\nThis article is about the migration pattern.\nNot about reinventing auth.</p>\n<p>The <strong>zero-downtime pattern</strong> looks like this:</p>\n<ol>\n<li>New format for new writes</li>\n<li>Support both formats for reads</li>\n<li>Migrate old data gradually (migrate-on-login is a great trick)</li>\n<li>Put it behind a feature flag</li>\n<li>Delete legacy when you are done</li>\n</ol>\n<p>By allowing the old and new formats to coexist for a period of time, you achieve a seamless transition.\nOnce your monitoring shows that 99% of active users have migrated,\nyou can identify the users on the legacy format and force a password reset on their next attempt.</p>\n<p>If you want to see a practical demo of this, <a href=\"https://youtu.be/7YUV4O9eMjQ\"><strong>check out this video I made</strong></a>.</p>\n<p>Hope this was helpful!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/a-practical-demo-of-zero-downtime-migrations-using-password-hashing",
            "title": "A Practical Demo of Zero-Downtime Migrations Using Password Hashing",
            "summary": "A practical demo of the zero-downtime migration pattern: run old + new password hash formats side-by-side, migrate users on login, and remove legacy safely.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_178.png",
            "date_modified": "2026-01-24T00:00:00.000Z",
            "date_published": "2026-01-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/solving-the-distributed-cache-invalidation-problem-with-redis-and-hybridcache",
            "content_html": "<p><code>HybridCache</code> in .NET 9 shipped without a built-in backplane, so updating data on one node leaves the other nodes serving stale data from their local L1 cache.\nThe fix is a Redis Pub/Sub backplane: the node that changes data publishes the cache key, and every node evicts that key locally.</p>\n<p>Distributed systems are great for scalability, but they introduce a whole new class of problems.\nOne of the hardest problems to solve is <strong>cache invalidation</strong>.</p>\n<p>In .NET 9, <a href=\"https://milanjovanovic.tech/blog/hybrid-cache-in-aspnetcore-new-caching-library\"><strong>Microsoft introduced <code>HybridCache</code></strong></a> to simplify caching.\nIt's a fantastic library that combines the speed of in-memory caching (L1) with the durability of distributed caching (L2) like Redis.\nIt also handles &quot;cache stampede&quot; protection out of the box.</p>\n<p>However, there is a catch.</p>\n<p>When you run multiple instances of your application, <code>HybridCache</code> doesn't automatically synchronize the local L1 cache across all nodes.\nIf you update data on <strong>Node A</strong>, <strong>Node B</strong> will continue serving stale data from its in-memory cache until the entry expires.</p>\n<p>While HybridCache is a massive step forward, the lack of a built-in backplane for invalidation is a known limitation.\nIn fact, there is an active discussion on the <a href=\"https://github.com/dotnet/extensions/issues/5517\">dotnet/extensions GitHub repository</a> tracking this exact feature request.\nUntil that ships, we have to roll our own solution.</p>\n<p>In this week's newsletter, we'll explore:</p>\n<ul>\n<li>The distributed caching dilemma</li>\n<li>Why <code>HybridCache</code> doesn't solve this alone</li>\n<li>Using Redis Pub/Sub as a backplane</li>\n<li>Implementing real-time cache invalidation</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Distributed Caching Dilemma</h2>\n<p>Let's imagine a typical production scenario.\nYou have an API running on multiple servers (or pods) behind a load balancer.</p>\n<p>To improve performance, you introduce <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>caching</strong></a>.\nYou want the speed of local memory, so you use <code>HybridCache</code>.</p>\n<p>Here is the failure scenario:</p>\n<ol>\n<li><strong>User A</strong> updates their profile on <strong>Server 1</strong>.</li>\n<li><strong>Server 1</strong> updates the database and clears its local cache.</li>\n<li><strong>User A</strong> (or User B) hits <strong>Server 2</strong>.</li>\n<li><strong>Server 2</strong> still holds the <em>old</em> profile data in its local <code>HybridCache</code>.</li>\n<li>The user sees outdated information, since the local cache hasn't been invalidated.</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_177/hybridcache_out_of_sync_scenario.png\" alt=\"A sequence diagram showing two servers with HybridCache out of sync after a user update.\">\n<p><strong>Why Not Just Shorten the Cache Duration?</strong></p>\n<p>A common &quot;hack&quot; to solve this is to simply reduce the L1 cache duration (TTL).\nFor example, setting the local cache to expire every 10 seconds.</p>\n<p>While this reduces the window of inconsistency, it doesn't solve the problem.\nIt just masks it.</p>\n<p>This approach introduces two new issues:</p>\n<ul>\n<li><strong>Increased Latency</strong>: You are now forcing your application to reach out to the distributed L2 cache (Redis) or the database much more frequently.</li>\n<li><strong>Lost Efficiency</strong>: The main benefit of L1 caching is avoiding network requests entirely.\nIf you expire data too fast, you lose the performance gain for the majority of your traffic.</li>\n</ul>\n<p>For things like user permissions, feature flags, or pricing, &quot;mostly correct&quot; is often not good enough. You need immediate consistency.</p>\n<h2>The Solution: Redis Pub/Sub Backplane</h2>\n<p>To solve this, we need a <strong>backplane</strong>.\nIt's a communication channel that connects all our application nodes.</p>\n<p>When a cache entry is removed or updated on one node, we publish a message to the backplane.\nAll other nodes subscribe to this channel and, upon receiving the message, remove the corresponding key from their local cache.</p>\n<p>Redis is already a popular choice for the L2 cache, so it makes perfect sense to use its <a href=\"https://milanjovanovic.tech/blog/simple-messaging-in-dotnet-with-redis-pubsub\"><strong>Pub/Sub feature</strong></a>\nfor this signaling mechanism.</p>\n<p>It works like this:</p>\n<ol>\n<li><strong>Publisher:</strong> The node that modifies data publishes a <code>cache-invalidation</code> message with the cache key.</li>\n<li><strong>Subscriber:</strong> All nodes listen to this channel.</li>\n<li><strong>Action:</strong> When a message arrives, they call <code>HybridCache.RemoveAsync(key)</code>.</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_177/cache_invalidation_message_fanout.png\" alt=\"A sequence diagram showing cache invalidation messages being published and received by multiple servers.\">\n<h2>Implementing the Solution</h2>\n<p>We will need the <code>StackExchange.Redis</code> library to handle the messaging.</p>\n<p>Let's start by defining a simple service to handle the publishing.\nThis service will be responsible for notifying the rest of the system that a key has changed.</p>\n<pre><code class=\"language-csharp\">public interface ICacheInvalidator\n{\n    Task InvalidateAsync(string key, CancellationToken cancellationToken = default);\n}\n\npublic class RedisCacheInvalidator(\n    IConnectionMultiplexer connectionMultiplexer,\n    ILogger&lt;RedisCacheInvalidator&gt; logger)\n    : ICacheInvalidator\n{\n    private const RedisChannel Channel = RedisChannel.Literal(&quot;cache-invalidation&quot;);\n\n    public async Task InvalidateAsync(string key, CancellationToken cancellationToken = default)\n    {\n        var subscriber = connectionMultiplexer.GetSubscriber();\n\n        await subscriber.PublishAsync(Channel, new RedisValue(key));\n\n        logger.LogInformation(&quot;Published invalidation for key: {Key}&quot;, key);\n    }\n}\n</code></pre>\n<p>Now, whenever you update an entity in your Command Handler or Service, you just call <code>ICacheInvalidator.InvalidateAsync</code>.</p>\n<pre><code class=\"language-csharp\">public class UpdateUserProfileHandler(\n    AppDbContext dbContext,\n    ICacheInvalidator cacheInvalidator,\n    ILogger&lt;UpdateUserProfileHandler&gt; logger)\n{\n    public async Task Handle(int userId, string newName, CancellationToken ct)\n    {\n        // 1. Update the database\n        var user = await dbContext.Users.FindAsync([userId], ct);\n        if (user is null)\n        {\n             return;\n        }\n\n        user.Name = newName;\n        await dbContext.SaveChangesAsync(ct);\n\n        // 2. Invalidate the cache (Distributed)\n        var cacheKey = $&quot;user:{userId}&quot;;\n        await cacheInvalidator.InvalidateAsync(cacheKey, ct);\n\n        logger.LogInformation(&quot;Updated user and invalidated cache for {UserId}&quot;, userId);\n    }\n}\n</code></pre>\n<h3>The Background Listener</h3>\n<p>Next, we need a <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>background service</strong></a> that runs on every node.\nIt will subscribe to the Redis channel and evict keys from the local <code>HybridCache</code>.</p>\n<p><strong>A quick note on self-publishing</strong>:\nBecause Redis Pub/Sub broadcasts to everyone subscribed, the node that published the invalidation will also receive the message.\nIn this implementation, we simply remove the key again.\nIt's redundant but harmless, and it keeps the code simple.</p>\n<p>Note that we are injecting <code>HybridCache</code> directly into our background service.\nAn alternative is using <code>IMemoryCache</code>, since that is the L1 cache inside <code>HybridCache</code>.</p>\n<pre><code class=\"language-csharp\">public class CacheInvalidationService(\n    IConnectionMultiplexer connectionMultiplexer,\n    HybridCache hybridCache,\n    ILogger&lt;CacheInvalidationService&gt; logger)\n    : BackgroundService\n{\n    private const RedisChannel Channel = RedisChannel.Literal(&quot;cache-invalidation&quot;);\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        var subscriber = connectionMultiplexer.GetSubscriber();\n\n        await subscriber.SubscribeAsync(Channel, (channel, value) =&gt;\n        {\n            string key = value.ToString();\n\n            logger.LogInformation(&quot;Invalidating local cache for: {Key}&quot;, key);\n\n            // This removes the item from the local L1 cache\n            var task = hybridCache.RemoveAsync(key, stoppingToken);\n\n            if (!task.IsCompleted)\n            {\n                task.GetAwaiter().GetResult();\n            }\n        });\n    }\n}\n\n</code></pre>\n<h3>Wiring It All Together</h3>\n<p>Finally, we need to register these services in our DI container.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSingleton&lt;IConnectionMultiplexer&gt;(sp =&gt;\n    ConnectionMultiplexer.Connect(&quot;&lt;REDIS_CONNECTION_STRING&gt;&quot;));\n\n// Register HybridCache (defaults generally work fine for L1)\nbuilder.Services.AddHybridCache();\n\n// Register our invalidation services\nbuilder.Services.AddSingleton&lt;ICacheInvalidator, RedisCacheInvalidator&gt;();\nbuilder.Services.AddHostedService&lt;CacheInvalidationService&gt;();\n\n</code></pre>\n<p>Now, when <strong>Node A</strong> calls <code>InvalidateAsync(&quot;user:123&quot;)</code>, Redis pushes that message to <strong>Node B</strong>, <strong>Node C</strong>, and so on.\nThey all trigger <code>hybridCache.RemoveAsync(&quot;user:123&quot;)</code>, ensuring the next request fetches fresh data from the source (or the shared L2).</p>\n<h2>A Better Way: FusionCache</h2>\n<p>If building your own backplane feels like reinventing the wheel, you should look at <a href=\"https://github.com/ZiggyCreatures/FusionCache\"><strong>FusionCache</strong></a>.</p>\n<p>FusionCache is a mature, battle-tested library that has solved this exact problem for years.\nIt has a built-in backplane feature that automatically handles the Pub/Sub messaging for you.</p>\n<p>Even better, FusionCache recently added an implementation of the HybridCache abstract class.\nThis means you can swap it in without changing much of your existing code.</p>\n<pre><code class=\"language-csharp\">// Using FusionCache's implementation of HybridCache\nbuilder.Services.AddFusionCache()\n    .WithBackplane(\n        new RedisBackplane(new RedisBackplaneOptions { Configuration = &quot;&lt;REDIS_CONNECTION_STRING&gt;&quot; }))\n    .AsHybridCache();\n</code></pre>\n<h2>Summary</h2>\n<p><code>HybridCache</code> is a powerful addition to the .NET ecosystem, effectively merging the benefits of <code>IMemoryCache</code> and <code>IDistributedCache</code>.\nHowever, for multi-node setups requiring high consistency, you still need a mechanism to synchronize the local caches.</p>\n<p>Redis Pub/Sub offers a lightweight, effective solution to this problem.</p>\n<p>By implementing a simple &quot;bus&quot; for invalidation messages, you get the best of both worlds:\nthe extreme performance of local caching and the data consistency of a distributed system.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/solving-the-distributed-cache-invalidation-problem-with-redis-and-hybridcache",
            "title": "Solving the Distributed Cache Invalidation Problem with Redis and HybridCache",
            "summary": "Learn how to solve the distributed cache invalidation problem in .NET 9 by implementing a Redis Pub/Sub backplane to synchronize HybridCache instances across…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_177.png",
            "date_modified": "2026-01-17T00:00:00.000Z",
            "date_published": "2026-01-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/solving-message-ordering-from-first-principles",
            "content_html": "<p>Global message ordering is rarely what you need.\nWhat you actually need is per-aggregate ordering: events for the same <code>OrderId</code> or <code>CustomerId</code> handled in sequence.\nThat means only one handler can process events for a given aggregate at a time, and following that requirement leads you to a saga.</p>\n<p>Most systems don't need <em>global</em> message ordering.</p>\n<p>They need something simpler and more useful: <strong>events must be handled in order per aggregate</strong>.</p>\n<p>Per <code>OrderId</code>, per <code>InvoiceId</code>, per <code>CustomerId</code>, or whatever your aggregate boundary is.\nYou can make this as broad or as narrow as you need.</p>\n<p>This starts as an eventing problem, but if you follow the requirements to their logical conclusion, you'll end up with a workflow.\nAnd that workflow has a name: <strong>a saga</strong>.</p>\n<h2>Domain Events Feel Like the Clean Solution</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>Domain events</strong></a> are attractive because they come from first principles:</p>\n<ul>\n<li>An aggregate changes state</li>\n<li>It emits events describing what happened</li>\n<li>Handlers react and do useful work</li>\n</ul>\n<p>You also get a nice mental model:</p>\n<blockquote>\n<p>State change → Event → Reaction</p>\n</blockquote>\n<p>A typical example:</p>\n<ul>\n<li><code>OrderPlaced</code></li>\n<li><code>PaymentCaptured</code></li>\n<li><code>OrderShipped</code></li>\n</ul>\n<p>But there's a catch.</p>\n<p>Domain events are <strong>brittle</strong> when you try to use them for integration.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/domain_events_flow.png\" alt=\"A sequence diagram showing an Aggregate committing a change, emitting Domain Events, which are then processed by multiple Event Handlers.\">\n<p>If you publish directly from your transaction, you're coupling business correctness to an unreliable side effect:</p>\n<ul>\n<li>The transaction succeeds but publishing fails</li>\n<li>Publishing succeeds but the transaction rolls back</li>\n<li>Consumers process duplicates</li>\n<li>Retries cause reordering</li>\n</ul>\n<p>So we keep the model, but harden the delivery.</p>\n<h2>The Outbox Makes Publishing Reliable (but not ordered)</h2>\n<p>With an <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox</strong></a>, we store outgoing events in the same transaction as the aggregate update.</p>\n<p>Then a background publisher reads the Outbox and pushes events to a queue.</p>\n<p>This fixes the reliability problem:</p>\n<ul>\n<li>If the transaction commits, the event is persisted</li>\n<li>If the publisher crashes, it can resume later</li>\n<li>We can retry safely</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/outbox_flow.png\" alt=\"A diagram showing an Aggregate committing a change along with an Outbox entry, which is then published to a Queue by a background process.\">\n<p>Now we've made event publishing reliable.</p>\n<p>But we haven't made event handling ordered.</p>\n<h2>Competing consumers Are Great, Until Order Matters</h2>\n<p>The moment events hit a queue, we usually scale with the simplest lever: <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>competing consumers</strong></a>.</p>\n<p>Multiple instances consume from the same queue to increase throughput.</p>\n<p>That works… until ordering matters.</p>\n<p>Two events for the same <code>OrderId</code> can be processed at the same time:</p>\n<ul>\n<li>Consumer A receives <code>PaymentCaptured</code></li>\n<li>Consumer B receives <code>OrderPlaced</code></li>\n<li>Side effects run out of order</li>\n</ul>\n<p>Even if the events were published in order, retries and redelivery can scramble processing order.</p>\n<p>And now you have a subtle bug that only appears under load.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/competing_consumers.png\" alt=\"A sequence diagram showing multiple consumers processing messages from a Queue concurrently, leading to out-of-order handling of events for the same aggregate ID.\">\n<p>That's the key realization: queues scale work.\nThey don't preserve your invariants.</p>\n<h2>What We Really Want is Per-Aggregate Ordering</h2>\n<p>You don't need one ordered line for <em>everything</em>.</p>\n<p>You need many independent ordered lines, one per aggregate.</p>\n<p>That usually holds true because:</p>\n<ul>\n<li>Aggregates already define consistency boundaries</li>\n<li>Events are naturally produced in order (v1, v2, v3…)</li>\n<li>The &quot;correct&quot; order is the aggregate's own timeline</li>\n</ul>\n<p>If we could guarantee that <strong>only one handler processes events for a given aggregate at a time</strong>, most of the problem disappears.</p>\n<p>The most direct solution is also the simplest: <strong>use a single consumer for the whole stream.</strong></p>\n<p>That enforces ordering, assuming events are published in order.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/single_consumer.png\" alt=\"A sequence diagram showing a single consumer processing messages from a Queue, leading to ordered handling of events for the same aggregate ID.\">\n<p>But it has an obvious drawback.</p>\n<h2>A Single Consumer Solves Ordering But Limits Scale</h2>\n<p>One consumer means:</p>\n<ul>\n<li>Throughput ceiling (one worker)</li>\n<li>Latency spikes under load</li>\n<li>Scaling becomes vertical, not horizontal</li>\n</ul>\n<p>Even if your events are lightweight, you're artificially bottlenecking the system.</p>\n<p>So we want:</p>\n<ul>\n<li><strong>Per-aggregate ordering</strong></li>\n<li><strong>Horizontal scaling</strong></li>\n<li><strong>Reliability (Outbox still stays)</strong></li>\n</ul>\n<p>This is where teams often &quot;invent&quot; the next step.</p>\n<h2>Publish the Next Message From the Handler</h2>\n<p>If competing consumers break ordering, one natural idea is:</p>\n<blockquote>\n<p>Don't let the queue decide what's next, we decide.</p>\n</blockquote>\n<p>Instead of dumping all events into the queue and letting consumers race, we move to a chained approach:</p>\n<ol>\n<li>Handle one message for an aggregate</li>\n<li>When done, publish the <strong>next</strong> message to be handled</li>\n</ol>\n<p>Now, the system processes a single message at a time per aggregate.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/choreographed_saga.png\" alt=\"A diagram showing a sequence of events being processed one at a time per aggregate ID, with each event handler publishing the next event upon completion.\">\n<p>This is the key moment:</p>\n<p>You've stopped building &quot;event handlers&quot;.</p>\n<p>You've started building a <strong>workflow</strong>.</p>\n<p>And that workflow is… a saga.</p>\n<h2>Congratulations, You Built a Choreographed Saga</h2>\n<p>A <a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\"><strong>choreographed saga</strong></a> is a workflow where:</p>\n<ul>\n<li>Each step reacts to an event</li>\n<li>Performs work</li>\n<li>Emits the next event to trigger the next step</li>\n</ul>\n<p>There isn't a single central coordinator.</p>\n<p>Instead, we have a chain of events: &quot;when X happens, do Y, then publish Z&quot;.</p>\n<p>This pattern naturally fits your new requirement:</p>\n<ul>\n<li>Per-aggregate ordering is preserved (the chain is sequential)</li>\n<li>You can scale across aggregates (many chains in flight)</li>\n<li>Each step is isolated and retryable</li>\n</ul>\n<p>It also forces a useful discipline:</p>\n<ul>\n<li>&quot;What's the next step?&quot; becomes explicit</li>\n<li>Boundaries between steps become clearer</li>\n<li>You can observe the workflow as a sequence</li>\n</ul>\n<p>But choreography has a limitation: <strong>control is distributed</strong>, so tracking progress and handling exceptions can get messy.</p>\n<p>So we take the final step.</p>\n<h2>If You Want Control, Introduce a State Machine Saga</h2>\n<p>When the workflow becomes important, you often want:</p>\n<ul>\n<li>A single place that knows the current state</li>\n<li>Visibility into progress (&quot;where are we stuck?&quot;)</li>\n<li>Explicit timeouts and retries</li>\n<li>Compensating actions when something fails</li>\n</ul>\n<p>That's when you move from choreography to <strong>orchestration</strong> via a <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>state machine saga</strong></a>:</p>\n<ul>\n<li>The saga holds the workflow state</li>\n<li>Events drive transitions</li>\n<li>The saga decides what message to publish next</li>\n<li>You gain control and observability</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_176/state_machine.png\" alt=\"A state machine diagram showing states and transitions for a saga managing ordered message processing per aggregate ID.\">\n<p>This doesn't replace the Outbox, by the way.</p>\n<p>You still want reliable publishing.</p>\n<p>You've just made the workflow explicit.</p>\n<h2>Broker Support Helps with Ordering, not Correctness</h2>\n<p>It's worth calling out that you don't always have to build this yourself.</p>\n<p>Many popular message brokers provide technical primitives for <strong>ordered processing per key</strong> (your aggregate ID):</p>\n<ul>\n<li><a href=\"https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html\">Amazon SQS FIFO message groups</a> (per key)</li>\n<li><a href=\"https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-sessions\">Azure Service Bus sessions</a> (per key)</li>\n<li><a href=\"https://developer.confluent.io/courses/apache-kafka/partitions/\">Kafka Partitions</a> in a log (key → partition → ordered stream)</li>\n<li><a href=\"https://www.rabbitmq.com/docs/consumers?#single-active-consumer\">RabbitMQ &quot;single active consumer&quot;</a> style semantics (per queue)</li>\n</ul>\n<p>These features can eliminate the most common failure mode of competing consumers: <strong>concurrent handling of messages for the same aggregate</strong>.</p>\n<p>But even with perfect per-aggregate ordering, you still need patterns around it to keep the system correct:</p>\n<ul>\n<li><strong>Outbox</strong> to publish reliably (ordering is useless if events are lost)</li>\n<li><a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>Idempotent consumers</strong></a> / <strong>Inbox</strong> because retries and duplicates still happen</li>\n<li><strong>Consistency boundaries</strong> (what's safe to do inside the transaction vs outside)</li>\n<li><strong>Timeouts</strong> + <strong>compensation</strong> when the &quot;ordered sequence&quot; is actually a business workflow that can partially fail</li>\n</ul>\n<p>So broker-level ordering is a great foundation.\nIt reduces accidental complexity.\nIt just doesn't remove the need to model long-running work explicitly when the business demands it.</p>\n<h2>Takeaway</h2>\n<p>If you follow the problem from first principles:</p>\n<ul>\n<li>Aggregates define the boundary where ordering matters</li>\n<li>The Outbox makes event publishing reliable</li>\n<li>Competing consumers break per-aggregate order</li>\n<li>A single consumer restores order but caps throughput</li>\n<li>Publishing &quot;the next message&quot; creates sequential progress per aggregate</li>\n<li>That sequential progress is a saga (choreographed first, state machine when you need control)</li>\n</ul>\n<p>So you didn't reinvent something by accident.</p>\n<p>You discovered that &quot;ordered handling per aggregate at scale&quot; is not a queue feature.</p>\n<p>It's a workflow. And sagas are how we model workflows in distributed systems.</p>\n<p>Once you see it that way, you stop fighting queues for ordering guarantees.</p>\n<p>You design the workflow the business actually needs.</p>\n<p>Hope this was helpful!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/solving-message-ordering-from-first-principles",
            "title": "Solving Message Ordering from First Principles",
            "summary": "Per-aggregate ordering is what we really want. But queues with competing consumers make it surprisingly easy to break.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_176.png",
            "date_modified": "2026-01-10T00:00:00.000Z",
            "date_published": "2026-01-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-urge-to-build-something",
            "content_html": "<p>There is a specific kind of restlessness that only developers understand.\nIt's a quiet, persistent itch.\nA mental &quot;background process&quot; that runs while you're eating, showering, or trying to sleep.\nI call it <strong>the urge to build</strong>.</p>\n<p>Looking back at <a href=\"https://github.com/m-jovanovic\">my GitHub contribution graphs</a> from the last few years, I see more than just green squares.\nI see a timeline of my own evolution.</p>\n<p>I was <strong>chasing that dream</strong> many developers have: build a small product, ship it, get users, maybe turn it into a business.\nMost of those ideas didn't become a successful SaaS.</p>\n<p>But that wasn't the point.</p>\n<p>The point was that I was building constantly.\nLearning constantly.\nShipping constantly.</p>\n<p>And I loved it.</p>\n<h2>The Indie Hacker Dream</h2>\n<p>Back in 2020, my contribution graph shows the classic &quot;burst&quot; pattern.\nI was chasing the indiehacker dream.\nYou know the one: build a SaaS, get to ramen profitability, escape the 9-to-5, tweet about your MRR milestones.\nI consumed every bootstrapper podcast, every &quot;I made $10K/month&quot; blog post, every ProductHunt launch breakdown.</p>\n<p>I was working on a project called <a href=\"https://github.com/m-jovanovic/expensely-server\">Expensely</a>.\nI spent late nights pouring myself into this &quot;genius&quot; idea: a budgeting app.</p>\n<p>Nothing revolutionary.\nThe world didn't need another expense tracker.\nBut I needed to build it.</p>\n<p>Looking back now, it was far from genius.\nBut at the time, I was convinced that this was <em>the one</em>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/github_contributions_2020.png\" alt=\"A GitHub contribution graph from 2020 showing a burst of activity in the end of the year.\">\n<p>In 2021, that momentum stayed steady for five months before life (or perhaps reality) intervened.\nThose green squares represent the <strong>fun periods</strong>.\nThey represent the thrill of architecting an application, the satisfaction of a passing test suite,\nand the hope that you're building something that might change your life.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/github_contributions_2021.png\" alt=\"A GitHub contribution graph from 2021 showing steady activity for the first five months and then a tapering off.\">\n<p>Expensely never became a business.\nMost side projects don't.\nBut here's what I've come to understand: the outcome wasn't the point.</p>\n<p>Even though those projects didn't become the next big SaaS, they were the forge where my skills were sharpened.\nYou don't &quot;waste&quot; time building something that fails.\nYou only waste time when you don't build at all.\nAnd I mean this both metaphorically and literally.\nHumans are meant to create.\nWhen we stop creating, we stagnate.</p>\n<h2>What Building Actually Gives You</h2>\n<p>When you build something from scratch - when you own every decision from the database schema to the button colors - you learn differently.\nThere's no senior developer to ask.\nNo established patterns to follow.\nEvery problem is yours to solve.</p>\n<p>You can use <a href=\"https://fsharpforfunandprofit.com/rop/\">Railway-Oriented Programming</a> if you want to, because who is going to stop you?</p>\n<p>Jokes aside, <strong>functional programming</strong> is <strong>very useful</strong>. You should learn it.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/rop_sample_code.png\" alt=\"A screenshot of sample code implementing Railway-Oriented Programming in C#.\">\n<p>Through my side projects, I wrestled with authentication, background jobs, payment integrations, deployment pipelines.\nI made mistakes and spent late nights fixing them.\nI built features nobody asked for and skipped features everyone needed.</p>\n<p>There is <strong>one thing side projects can't teach you</strong> (unless you get users): dealing with the <strong>consequences of your decisions</strong>.\nWhen you build for yourself, the only person affected by your mistakes is you.\nWhen you build for users, every bug, every outage, every poor design choice has real consequences.</p>\n<p>By mid 2021, the project had wound down.\nI was settling into a comfortable routine at my day job, working as a senior engineer at a big corporation.\nThere was no time left for side projects.</p>\n<h2>The Unexpected Pivot</h2>\n<p>By 2022, something changed.\nMy contribution graph exploded to over 1,600 commits.\nBut the focus shifted.\nI wasn't building a product anymore.\nI was building something more meaningful.\nThe repositories shifted to my tech blog and <a href=\"https://www.youtube.com/@MilanJovanovicTech\">YouTube projects</a>.</p>\n<p>I had <strong>discovered</strong> content creation.</p>\n<p>The urge to build hadn't disappeared.\nI found a new outlet.\nInstead of building products for users, I was building educational content for developers.\nInstead of SaaS metrics, I was tracking video views and newsletter subscribers.\nTo me it was something new, something exciting.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/github_contributions_2022.png\" alt=\"A GitHub contribution graph from 2022 showing steady activity throughout the year.\">\n<p>I discovered that while I loved building software, I loved <strong>explaining</strong> it even more.\nI transitioned from an aspiring founder to a software engineering educator.\nAt first it was just me writing and recording <strong>things I wish someone had explained to me earlier</strong>.</p>\n<p>Then something interesting happened.</p>\n<p>People started reading.\nWatching.\nReplying.\nAsking questions.\nMore people showed up.\nAnd then even more.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/youtube_milanjovanovictech.png\" alt=\"A screenshot of the Milan Jovanovic Tech YouTube channel with over 140,000 subscribers.\">\n<p>Today, I get to help thousands of developers around the world improve their careers.\nIt is, without a doubt, the <strong>most rewarding work I've ever done</strong>.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_175/pca_testimonials.png\" alt=\"A screenshot of testimonials from students of the Pragmatic Clean Architecture course.\">\n</div>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_175/pca_testimonials.png\" alt=\"A screenshot of testimonials from students of the Pragmatic Clean Architecture course.\">\n  <figcaption>\n    Student testimonials from <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean\nArchitecture</strong></a>.\n  </figcaption>\n</figure>\n<p>This is genuinely fulfilling work.\nWhen someone messages me saying my content helped them land a job\nor finally understand a concept they'd struggled with for years, that feeling is hard to describe.\nI'm doing something meaningful.</p>\n<p>And yet.</p>\n<h2>The Lingering Itch</h2>\n<p>The urge to build something still remains.</p>\n<p>It's not dissatisfaction.\nIt's not that content creation isn't &quot;real&quot; building, it absolutely is.\nIt's a different kind of building, certainly.\nBut this feeling I have is something more fundamental.\nA part of my brain that wants to work on a fresh project.\nThat wants to solve a problem nobody has asked me to solve.\nThat wants to take an idea from nothing to something.</p>\n<p>That's why I have a <a href=\"https://www.hetzner.com/\">Hetzner</a> server sitting idle right now.\nNever know when I might need it.\nRight?\nAlso, I'm getting a great deal for €3.29/month.\nCan't argue with that.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_175/hetzner_server.png\" alt=\"A screenshot of a Hetzner server management dashboard showing a server.\">\n<p>I think this urge never fully goes away for people like us.\nWe can channel it, redirect it, find new outlets for it.\nBut it's always there, lingering.\nAnd maybe that's okay.\nMaybe that restlessness is what makes us who we are.</p>\n<h2>Why You Should Build Too</h2>\n<p>If you're reading this with your own idea rattling around in your head - some app concept, some tool you wish existed,\nsome problem you think you could solve - I want to tell you something.</p>\n<p><strong>Build it.</strong></p>\n<p>Not because it will make you rich.\nStatistically, it won't.\nNot because it will become the next big thing.\nIt probably won't be that either.</p>\n<p><strong>Build it because the person who finishes that project will not be the same person who started it.</strong>\nYou will learn things no tutorial can teach.\nYou will develop judgment that only comes from making real decisions with real consequences.\nYou will have stories, opinions, and experiences that set you apart.</p>\n<p>The outcome is almost beside the point.\nThe transformation is the product.</p>\n<p>Maybe you'll end up with a successful SaaS.\nMaybe you'll end up with a failed project and a mass of hard-won knowledge.\nMaybe, like me, you'll end up somewhere you never expected - doing work you couldn't have imagined when you wrote that first line of code.</p>\n<p>You won't know until you build.</p>\n<p>So open your terminal.\nType <code>git init</code>.\nAnd start.</p>\n<p>Let this be a year of building.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-urge-to-build-something",
            "title": "The Urge to Build Something",
            "summary": "Looking at my contribution graph now, I can see the whole journey mapped out in squares of green. The startup dreams. The pivot to teaching.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_175.png",
            "date_modified": "2026-01-03T00:00:00.000Z",
            "date_published": "2026-01-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-build-a-high-performance-cache-without-external-libraries",
            "content_html": "<p>A <code>ConcurrentDictionary</code> keeps the cache thread-safe, but it does not stop 100 requests from missing at once and calling the API 100 times.\nWrap entries with a timestamp for expiration, then guard refreshes with double-checked locking on a <code>SemaphoreSlim</code> per key, so one caller fetches while the rest wait.\nProduction code should use <code>HybridCache</code> or FusionCache.</p>\n<p>A couple of days ago, I was looking at a piece of code that's doing too much work.\nI'm sure you'll be able to draw a parallel to something in your own applications.\nMaybe it's a database call that should be faster, or an external API that's starting to bill you by the thousands.</p>\n<p>My first instinct is: <strong>&quot;I'll just cache it&quot;</strong>.</p>\n<p>In .NET, that usually means reaching for <code>IMemoryCache</code> or plugging in a distributed cache like Redis.</p>\n<p>But have you ever stopped to wonder what's actually happening inside those libraries?<br>\nWhy do we need all that complexity just to store a value in memory?</p>\n<p>So I spent the afternoon trying to build a <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>high-performance cache</strong></a> from scratch.</p>\n<p>I don't recommend DIY-ing your own caching library for production use.\nBut I learn best by doing something myself.\nUnderstanding these patterns (concurrency, race conditions, and <a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6\"><strong>locking</strong></a>)\nis what separates a &quot;coder&quot; from an engineer.</p>\n<h2>The Starting Point</h2>\n<p>I was working on a simple currency conversion handler.\nWe're calling a third-party API to get exchange rates.\nThe API returns the current exchange rate for a given currency code (like EUR, GBP, JPY) against USD.</p>\n<p>This is the initial implementation:</p>\n<pre><code class=\"language-csharp\">public static class CurrencyConversion\n{\n    public static async Task&lt;IResult&gt; Handle(\n\t\tstring currencyCode,\n\t\tdecimal amount,\n\t\tCurrencyApiClient currencyClient)\n\t{\n\t\t// Validate currency code format (3 uppercase letters)\n\t\tif (string.IsNullOrWhiteSpace(currencyCode) ||\n\t\t\tcurrencyCode.Length != 3 ||\n\t\t\t!currencyCode.All(char.IsLetter))\n\t\t{\n\t\t\treturn Results.BadRequest(\n\t\t\t\tnew { error = &quot;Currency code must be a 3-letter uppercase code (e.g., EUR, GBP)&quot; });\n\t\t}\n\n\t\t// Validate amount (must be positive)\n\t\tif (amount &lt; 0)\n\t\t{\n\t\t\treturn Results.BadRequest(new { error = &quot;Amount must be a positive number&quot; });\n\t\t}\n\n\t\tvar rate = await currencyClient.GetExchangeRateAsync(currencyCode);\n\n\t\tif (rate == null)\n\t\t{\n\t\t\treturn Results.NotFound(\n\t\t\t\tnew { error = $&quot;Exchange rate for {currencyCode} not found or API error occurred&quot; });\n\t\t}\n\n\t\tvar convertedAmount = amount * rate.Value;\n\n\t\treturn Results.Ok(new ExchangeRateResponse(\n\t\t\tCurrency: currencyCode,\n\t\t\tBaseCurrency: &quot;USD&quot;,\n\t\t\tRate: rate.Value,\n\t\t\tAmount: amount,\n\t\t\tConvertedAmount: convertedAmount\n        ));\n    }\n}\n</code></pre>\n<p>This works fine in your local dev environment.\nBut in production, if 100 people hit this at the same time, you're making 100 identical network calls.\nYour API provider will hate you (and you may even get rate limited), and your latency will spike.</p>\n<p>Now let's build a cache to fix this without using any external libraries.\nRemember, we're doing this for learning purposes only.</p>\n<h2>Level 1: Adding a <code>ConcurrentDictionary</code></h2>\n<p>Your first thought is probably to store the rates in a <code>ConcurrentDictionary</code>. It's thread-safe, so it feels like the right tool.</p>\n<pre><code class=\"language-csharp\">private static readonly ConcurrentDictionary&lt;string, decimal&gt; Cache = new();\n\n// In the Handler:\nif (Cache.TryGetValue(currencyCode, out var cachedRate))\n{\n\treturn cachedRate;\n}\n\nvar rate = await currencyClient.GetExchangeRateAsync(currencyCode);\n\nCache.TryAdd(currencyCode, rate.Value);\n</code></pre>\n<p>This definitely helps with performance under load.\nMultiple threads can read and write to the dictionary without crashing.\nBut <code>ConcurrentDictionary</code> protects the <em>dictionary structure</em>, not your <em>logic</em>.</p>\n<p>If 100 users request &quot;EUR&quot; at the exact same time, <code>TryGetValue</code> will return false for all of them.\nThey will all proceed to call the API.\nThis is a classic <a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\"><strong>race condition</strong></a>.\nYou've protected your memory, but you haven't protected the external API.</p>\n<p>There's also another problem with this approach: the rates never expire.</p>\n<h2>Level 2: Adding Cache Expiration</h2>\n<p>Currency rates don't stay the same forever.\nWe need a way to expire them.\nSince <code>ConcurrentDictionary</code> doesn't have a &quot;Time to Live&quot; (TTL), we have to wrap our data.</p>\n<pre><code class=\"language-csharp\">// Store both the rate and the time it was created\nprivate record CacheEntry(decimal Rate, DateTime CreatedAt);\n\n// Our cache now stores CacheEntry objects\nprivate static readonly ConcurrentDictionary&lt;string, CacheEntry&gt; Cache = new();\nprivate static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);\n\n// Check: Is it there? And is it still &quot;fresh&quot;?\nif (Cache.TryGetValue(currencyCode, out var entry) &amp;&amp;\n    (DateTime.UtcNow - entry.CreatedAt) &lt; CacheDuration)\n{\n\treturn entry.Rate;\n}\n</code></pre>\n<p>Now we have expiration.\nBut we've actually created a new problem: <a href=\"https://en.wikipedia.org/wiki/Thundering_herd_problem\">The Thundering Herd</a> (a.k.a <a href=\"https://en.wikipedia.org/wiki/Cache_stampede\">Cache Stampede</a>).</p>\n<p>Every 5 minutes, when the cache expires, all incoming traffic will see &quot;stale&quot; data and try to refresh it at once.</p>\n<p>So we need to fix that next.</p>\n<h2>Level 3: Solving the &quot;Cache Stampede&quot;</h2>\n<p>To fix the stampede, we need to ensure that only <em>one</em> person can fetch the update while everyone else waits.</p>\n<p>How do we do that in C#?</p>\n<p>We use a <code>SemaphoreSlim</code> and a pattern called <a href=\"https://en.wikipedia.org/wiki/Double-checked_locking\">Double-Checked Locking</a>.\nWe check the cache once (the &quot;fast path&quot;), then we lock, and then we check <em>again</em> to see if someone else filled it while we were waiting for the lock.</p>\n<pre><code class=\"language-csharp\">// Basically a mutex but async-friendly\nprivate static readonly SemaphoreSlim Lock = new(1, 1);\n\npublic static async Task&lt;decimal&gt; GetRateAsync(string code, CurrencyApiClient client)\n{\n\t// Fast path: No locking needed\n\tif (Cache.TryGetValue(code, out var entry) &amp;&amp; IsFresh(entry))\n\t{\n\t\treturn entry.Rate;\n\t}\n\n    var acquired = await Lock.WaitAsync(TimeSpan.FromSeconds(10)); // Avoid deadlocks\n    if (!acquired)\n    {\n        throw new Exception(&quot;Could not acquire lock to fetch exchange rate.&quot;);\n    }\n\ttry\n\t{\n\t\t// Double-check: Did someone else finish the API call while we waited?\n\t\tif (Cache.TryGetValue(code, out entry) &amp;&amp; IsFresh(entry))\n\t\t{\n\t\t\treturn entry.Rate;\n\t\t}\n\n\t\tvar rate = await client.GetExchangeRateAsync(code);\n\t\tvar newEntry = new CacheEntry(rate.Value, DateTime.UtcNow);\n\n\t\t// Atomically update the cache\n\t\t// This is safe because we're inside the lock\n\t\tCache.AddOrUpdate(code, newEntry, (_, _) =&gt; newEntry);\n\t\treturn rate.Value;\n\t}\n\tfinally\n\t{\n\t\t// Always release the lock\n\t\tLock.Release();\n\t}\n}\n</code></pre>\n<p>This is an improvement.\nBut something still feels off.</p>\n<p>Can you spot the <em>problem</em> with this code?</p>\n<p>Our lock behaves like a global lock.\nThis means that if one thread is fetching &quot;EUR&quot;, all other threads (even those requesting &quot;JPY&quot;) are blocked until the &quot;EUR&quot; fetch completes.\nThis problem is called <strong>lock contention</strong>.</p>\n<p>Let's fix that next.</p>\n<h2>Level 4: Scaling with Keyed Locking</h2>\n<p>The &quot;pro&quot; move here is <strong>Keyed Locking</strong>.\nWe create a lock for every specific currency.\nSince the number of currencies is finite, this isn't too memory-intensive.</p>\n<p>We need an additional <code>ConcurrentDictionary</code> to hold our semaphores, per currency code.</p>\n<pre><code class=\"language-csharp\">private static readonly ConcurrentDictionary&lt;string, SemaphoreSlim&gt; Locks = new();\n\n// In the Handler:\nvar semaphore = Locks.GetOrAdd(currencyCode, _ =&gt; new SemaphoreSlim(1, 1));\nif (!Cache.TryGetValue(currencyCode, out var cachedRate) &amp;&amp;\n\tDateTime.UtcNow - cachedRate?.CreatedAt &lt; CacheDuration)\n{\n    var acquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(10));\n    if (!acquired)\n    {\n        throw new Exception(&quot;Could not acquire lock to fetch exchange rate.&quot;);\n    }\n\ttry\n\t{\n\t\t// Fetch and update logic...\n\t}\n\tfinally { semaphore.Release(); }\n}\n</code></pre>\n<p>The only thing that changes is how we acquire the lock.\nNow, if one thread is fetching &quot;EUR&quot;, other threads requesting &quot;JPY&quot; can proceed without waiting.\nThis is the most scalable version of our cache.</p>\n<p><strong>But...</strong> It only works in memory.\nSo it's not suitable for distributed systems or multiple server instances.\nThere are also a few more edge cases to consider, but you can explore those as an exercise.</p>\n<h2>The Final Code</h2>\n<p>Here's the final version of our caching logic:</p>\n<pre><code class=\"language-csharp\">public static class CurrencyConversion\n{\n    private record CacheEntry(decimal Rate, DateTime CreatedAt);\n    private static readonly ConcurrentDictionary&lt;string, CacheEntry&gt; Cache = new();\n    private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);\n\n    private static readonly ConcurrentDictionary&lt;string, SemaphoreSlim&gt; Locks = new();\n\n    public static async Task&lt;IResult&gt; Handle(\n        string currencyCode,\n        decimal amount,\n        CurrencyApiClient currencyClient)\n    {\n        // Validate currency code format (3 uppercase letters)\n        if (string.IsNullOrWhiteSpace(currencyCode) ||\n            currencyCode.Length != 3 ||\n            !currencyCode.All(char.IsLetter))\n        {\n            return Results.BadRequest(\n                new { error = &quot;Currency code must be a 3-letter uppercase code (e.g., EUR, GBP)&quot; });\n        }\n\n        // Validate amount (must be positive)\n        if (amount &lt; 0)\n        {\n            return Results.BadRequest(new { error = &quot;Amount must be a positive number&quot; });\n        }\n\n        decimal? rate;\n        var semaphore = Locks.GetOrAdd(currencyCode, _ =&gt; new SemaphoreSlim(1, 1));\n        if (!Cache.TryGetValue(currencyCode, out var cachedRate) &amp;&amp;\n            DateTime.UtcNow - cachedRate?.CreatedAt &lt; CacheDuration)\n        {\n            var acquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(10));\n            if (!acquired)\n            {\n                throw new Exception(&quot;Could not acquire lock to fetch exchange rate.&quot;);\n            }\n\n            try\n            {\n                // Double-check locking pattern: check again inside the lock\n                if (!Cache.TryGetValue(currencyCode, out cachedRate) &amp;&amp;\n                    DateTime.UtcNow - cachedRate?.CreatedAt &lt; CacheDuration)\n                {\n                    rate = await currencyClient.GetExchangeRateAsync(currencyCode);\n\n                    if (rate == null)\n                    {\n                        return Results.NotFound(\n                            new { error = $&quot;Exchange rate for {currencyCode} not found or API error occurred&quot; });\n                    }\n\n                    Cache.AddOrUpdate(currencyCode,\n                        _ =&gt; new CacheEntry(rate.Value, DateTime.UtcNow),\n                        (_, _) =&gt; new CacheEntry(rate.Value, DateTime.UtcNow));\n                }\n                else\n                {\n                    rate = cachedRate!.Rate;\n                }\n            }\n            finally\n            {\n                semaphore.Release();\n            }\n        }\n        else\n        {\n            rate = cachedRate!.Rate;\n        }\n\n        var convertedAmount = amount * rate.Value;\n\n        return Results.Ok(new ExchangeRateResponse(\n            Currency: currencyCode,\n            BaseCurrency: &quot;USD&quot;,\n            Rate: rate.Value,\n            Amount: amount,\n            ConvertedAmount: convertedAmount\n        ));\n    }\n}\n</code></pre>\n<p>The next step would be to extract the core caching logic into its own reusable class.\nThat way, you can use it in other parts of your application.</p>\n<h2>Takeaway</h2>\n<p>Why go through all this trouble?</p>\n<p>It's easy to look at a simple <code>ConcurrentDictionary</code> and think you're done.\nBut as we've seen, the gap between &quot;it works&quot; and &quot;it scales&quot; is filled with edge cases that can bring a production system to its knees.</p>\n<p>When you use a library, it handles these edge cases for you.\nBut building it yourself teaches you about the &quot;three pillars&quot; of high-performance code:</p>\n<ol>\n<li><strong>Thread safety</strong></li>\n<li><strong>Lock contention</strong></li>\n<li><strong>Resource protection</strong></li>\n</ol>\n<p>Sometimes the most &quot;boring&quot; parts of our infrastructure, like a cache, are actually the most architecturally interesting.</p>\n<p>There's also that 1% of the time when you need a custom solution that no library can provide.\nSo it's worth knowing the fundamentals of how these things work.</p>\n<p>Modern libraries like <a href=\"https://milanjovanovic.tech/blog/hybrid-cache-in-aspnetcore-new-caching-library\"><strong>HybridCache</strong></a> or\n<a href=\"https://github.com/ZiggyCreatures/FusionCache\">FusionCache</a> handle this for you,\nbut understanding these patterns ensures you know exactly why your application behaves the way it does under load.</p>\n<p>And since this is the last issue of the year, I wish you a fantastic New Year! 🎉</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-build-a-high-performance-cache-without-external-libraries",
            "title": "How to Build a High-Performance Cache Without External Libraries",
            "summary": "Learn how to build a high-performance cache from scratch in .NET, moving from a simple ConcurrentDictionary to an optimized keyed-locking system.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_174.png",
            "date_modified": "2025-12-27T00:00:00.000Z",
            "date_published": "2025-12-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/server-sent-events-in-aspnetcore-and-dotnet-10",
            "content_html": "<p>Server-Sent Events stream data one way, from server to client, over a standard HTTP request with a <code>text/event-stream</code> content type.\nASP.NET Core 10 adds a native API: return <code>Results.ServerSentEvents</code> with an <code>IAsyncEnumerable&lt;T&gt;</code>, and the browser consumes it with <code>EventSource</code>, no client library required.\nSignalR is still the choice when you need two-way communication.</p>\n<p><strong>Real-time updates</strong> are no longer a &quot;nice-to-have&quot; feature.\nMost modern UI applications expect live data streams of some kind from the server.\nFor years, the go-to answer in the .NET ecosystem has been <a href=\"https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr\"><strong>SignalR</strong></a>.\nWhile SignalR is incredibly powerful, it's <em>nice to have</em> other <strong>options</strong> for simpler use cases.</p>\n<p>With the release of ASP.NET Core 10, we finally have a native, high-level API for <strong>Server-Sent Events</strong> (SSE).\nIt bridges the gap between basic HTTP polling and full-duplex WebSockets via SignalR.</p>\n<h2>Why SSE Instead of SignalR?</h2>\n<p><a href=\"https://dotnet.microsoft.com/en-us/apps/aspnet/signalr\">SignalR</a> is a powerhouse that handles\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API\">WebSockets</a>, Long Polling, and\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events\">SSE</a> automatically, providing a full-duplex (<strong>two-way</strong>) communication channel.\nHowever, it comes with a footprint: a specific protocol (Hubs), a required client-side library, and a need for &quot;sticky sessions&quot; or a backplane (like Redis) for scaling.</p>\n<p>SSE is different because:</p>\n<ul>\n<li><strong>Unidirectional</strong>: It's designed <strong>specifically</strong> for streaming data <strong>from the server to the client</strong>.</li>\n<li><strong>Native HTTP</strong>: It's just a standard HTTP request with a <code>text/event-stream</code> content type. No custom protocols.</li>\n<li><strong>Automatic Reconnection</strong>: Browsers natively handle reconnections via the <strong>EventSource</strong> API.</li>\n<li><strong>Lightweight</strong>: No heavy client libraries or complex handshake logic.</li>\n</ul>\n<h2>The Simplest Server-Sent Events Endpoint</h2>\n<p>The beauty of the .NET 10 SSE API is its simplicity.\nYou can use the new <code>Results.ServerSentEvents</code> to return a stream of events from any <code>IAsyncEnumerable&lt;T&gt;</code>.\nBecause <code>IAsyncEnumerable</code> represents a stream of data that can arrive over time,\nthe server knows to keep the HTTP connection open rather than closing it after the first &quot;chunk&quot; of data.</p>\n<p>Here's a minimal example of an SSE endpoint that streams order placements in real-time:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;orders/realtime&quot;, (\n\tChannelReader&lt;OrderPlacement&gt; channelReader,\n\tCancellationToken cancellationToken) =&gt;\n{\n\t// 1. ReadAllAsync returns an IAsyncEnumerable\n\t// 2. Results.ServerSentEvents tells the browser: &quot;Keep this connection open&quot;\n\t// 3. New data is pushed to the client as soon as it enters the channel\n\treturn Results.ServerSentEvents(\n        channelReader.ReadAllAsync(cancellationToken),\n        eventType: &quot;orders&quot;);\n});\n</code></pre>\n<p>When a client hits this endpoint:</p>\n<ol>\n<li>The server sends a <code>Content-Type: text/event-stream</code> header.</li>\n<li>The connection stays active and idle while waiting for data.</li>\n<li>As soon as your application pushes an order into the <code>Channel</code>, the <code>IAsyncEnumerable</code> yields that item,\nand .NET immediately flushes it down the open HTTP pipe to the browser.</li>\n</ol>\n<p>It's an incredibly efficient way to handle &quot;push&quot; notifications without the overhead of a stateful protocol.</p>\n<p>I'm using a <a href=\"https://milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels\"><strong><code>Channel</code></strong></a> here as a means to an end.\nIn a real application, you might have a <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>background service</strong></a>\nthat listens to a message queue (like RabbitMQ or Azure Service Bus)\nor a database change feed, and pushes new events into the channel for connected clients to consume.</p>\n<h2>Handling Missed Events</h2>\n<p>The simple endpoint we just built is great, but it has one weakness: it's missing resilience.</p>\n<p>One of the biggest challenges with real-time streams is connection drops.\nBy the time the browser automatically reconnects, several events might have already been sent and lost.\nTo solve this, SSE has a built-in mechanism: the <code>Last-Event-ID</code> <strong>header</strong>.\nWhen a browser reconnects, it sends this ID back to the server.</p>\n<p>In .NET 10, we can use the <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.net.serversentevents.sseitem-1?view=net-10.0\"><code>SseItem&lt;T&gt;</code></a>\ntype to wrap our data with metadata like IDs and retry intervals.</p>\n<p>By combining a simple in-memory <strong>OrderEventBuffer</strong> with the <strong>Last-Event-ID</strong> provided by the browser, we can &quot;replay&quot; missed messages upon reconnection:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;orders/realtime/with-replays&quot;, (\n\tChannelReader&lt;OrderPlacement&gt; channelReader,\n\tOrderEventBuffer eventBuffer,\n\t[FromHeader(Name = &quot;Last-Event-ID&quot;)] string? lastEventId,\n\tCancellationToken cancellationToken) =&gt;\n{\n\tasync IAsyncEnumerable&lt;SseItem&lt;OrderPlacement&gt;&gt; StreamEvents()\n\t{\n\t\t// 1. Replay missed events from the buffer\n\t\tif (!string.IsNullOrWhiteSpace(lastEventId))\n\t\t{\n\t\t\tvar missedEvents = eventBuffer.GetEventsAfter(lastEventId);\n\t\t\tforeach (var missedEvent in missedEvents)\n\t\t\t{\n\t\t\t\tyield return missedEvent;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Stream new events as they arrive in the Channel\n\t\tawait foreach (var order in channelReader.ReadAllAsync(cancellationToken))\n\t\t{\n\t\t\tvar sseItem = eventBuffer.Add(order); // Buffer assigns a unique ID\n\t\t\tyield return sseItem;\n\t\t}\n\t}\n\n\treturn TypedResults.ServerSentEvents(StreamEvents(), &quot;orders&quot;);\n});\n</code></pre>\n<h2>Filtering Server-Sent Events by User</h2>\n<p>Server-Sent Events is built on top of standard HTTP.\nBecause it is a standard <code>GET</code> request, your existing infrastructure &quot;just works&quot;:</p>\n<ul>\n<li><strong>Security</strong>: You can pass a standard <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT</strong></a> in the <code>Authorization</code> header.</li>\n<li><a href=\"https://milanjovanovic.tech/blog/getting-the-current-user-in-clean-architecture\"><strong>User Context</strong></a>: You can access <code>HttpContext.User</code> to extract a User ID and filter the stream.\nYou only send a user the data that belongs to them.</li>\n</ul>\n<p>Here's an example of an SSE endpoint that streams only the orders for the authenticated user:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;orders/realtime&quot;, (\n\tChannelReader&lt;OrderPlacement&gt; channelReader,\n\tIUserContext userContext, // Injected context containing user metadata\n\tCancellationToken cancellationToken) =&gt;\n{\n\t// The UserId is extracted from the JWT access token by the IUserContext\n\tvar currentUserId = userContext.UserId;\n\n\tasync IAsyncEnumerable&lt;OrderPlacement&gt; GetUserOrders()\n\t{\n\t\tawait foreach (var order in channelReader.ReadAllAsync(cancellationToken))\n\t\t{\n\t\t\t// We only yield data that belongs to the authenticated user\n\t\t\tif (order.CustomerId == currentUserId)\n\t\t\t{\n\t\t\t\tyield return order;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Results.ServerSentEvents(GetUserOrders(), &quot;orders&quot;);\n})\n.RequireAuthorization(); // Standard ASP.NET Core Authorization\n</code></pre>\n<p>Note that when you write a message to a <code>Channel</code> it's broadcast to <strong>all</strong> connected clients.\nThis isn't ideal for per-user streams.\nYou'll probably want to use something more robust for production.</p>\n<h2>Consuming Server-Sent Events in JavaScript</h2>\n<p>On the client side, you don't need to install a single npm package.\nThe browser's native <code>EventSource</code> API handles the heavy lifting, including the &quot;reconnect and send Last-Event-ID&quot; logic we discussed above.</p>\n<pre><code class=\"language-javascript\">const eventSource = new EventSource('/orders/realtime/with-replays');\n\n// Listen for the specific 'orders' event type we defined in C#\neventSource.addEventListener('orders', (event) =&gt; {\n  const payload = JSON.parse(event.data);\n  console.log(`New Order ${event.lastEventId}:`, payload.data);\n});\n\n// Do something when the connection opens\neventSource.onopen = () =&gt; {\n  console.log('Connection opened');\n};\n\n// Handle generic messages (if any)\neventSource.onmessage = (event) =&gt; {\n  console.log('Received message:', event);\n};\n\n// Handle errors and reconnections\neventSource.onerror = () =&gt; {\n  if (eventSource.readyState === EventSource.CONNECTING) {\n    console.log('Reconnecting...');\n  }\n};\n</code></pre>\n<h2>Summary</h2>\n<p>SSE in .NET 10 is the perfect middle ground for simple, one-way updates like dashboards, notification bells, and progress bars.\nIt's lightweight, HTTP-native, and easy to secure using your existing middleware.</p>\n<p>However, <strong>SignalR</strong> remains the robust, battle-tested choice for complex bi-directional communication or massive scale requiring a backplane.</p>\n<p>The goal isn't to replace SignalR, but to give you a simpler tool for simpler jobs.\nChoose the lightest tool that solves your problem.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/server-sent-events-in-aspnetcore-and-dotnet-10",
            "title": "Server-Sent Events in ASP.NET Core and .NET 10",
            "summary": "ASP.NET Core 10 introduces native Server-Sent Events as a lightweight, HTTP-native alternative to SignalR for simple one-way real-time updates like dashboards…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_173.png",
            "date_modified": "2025-12-20T00:00:00.000Z",
            "date_published": "2025-12-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-new-slnx-solution-format-migration-guide",
            "content_html": "<p><code>.slnx</code> is Microsoft's XML-based solution format: a plain list of projects and folders that reads like a <code>.csproj</code> and drops the GUID blocks that cause merge conflicts.\nWith the .NET 9 SDK (9.0.200 or later), <code>dotnet sln migrate</code> generates a <code>.slnx</code> next to your existing <code>.sln</code>.\nDelete the old <code>.sln</code> afterwards.</p>\n<p><strong>Solution files</strong> have always been <em>that one file</em> nobody wants to touch during a <strong>merge conflict</strong>.\nI still remember the pain of resolving conflicts in large monorepo solutions with hundreds of projects.\nCan I just say this was not fun?</p>\n<p>Microsoft is (finally!) <a href=\"https://devblogs.microsoft.com/visualstudio/new-simpler-solution-file-format/\">addressing that</a> with <strong><code>.slnx</code></strong>: an <strong>XML-based</strong>, simpler solution format designed to be easier to read, edit, and merge.</p>\n<p>Here is your practical guide to the future of .NET solutions.</p>\n<h2>The problem with <code>.sln</code></h2>\n<p>Classic <code>.sln</code> files are verbose: GUID-heavy project entries + configuration blocks that explode as your solution grows.\nIt's also a frequent source of merge conflicts.</p>\n<p>To appreciate the new format, we must look at the old one.</p>\n<p>Here's a typical <code>.sln</code> file from a moderately sized .NET solution:</p>\n<pre><code class=\"language-txt\">Microsoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.7.34031.279\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(&quot;{2150E333-8FDC-42A3-9474-1A3956D46DE8}&quot;) = &quot;Solution Items&quot;, &quot;Solution Items&quot;, &quot;{8FC526EA-218B-4615-8410-4E1850611F38}&quot;\n\tProjectSection(SolutionItems) = preProject\n\t\t.editorconfig = .editorconfig\n\t\tDirectory.Build.props = Directory.Build.props\n\t\tDirectory.Packages.props = Directory.Packages.props\n\tEndProjectSection\nEndProject\nProject(&quot;{2150E333-8FDC-42A3-9474-1A3956D46DE8}&quot;) = &quot;src&quot;, &quot;src&quot;, &quot;{64A28C1B-09AF-426E-8721-D002BE554B48}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;SharedKernel&quot;, &quot;src\\SharedKernel\\SharedKernel.csproj&quot;, &quot;{166778A2-518F-47F0-BBC7-DB49C76A963C}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;Domain&quot;, &quot;src\\Domain\\Domain.csproj&quot;, &quot;{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;Application&quot;, &quot;src\\Application\\Application.csproj&quot;, &quot;{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;Infrastructure&quot;, &quot;src\\Infrastructure\\Infrastructure.csproj&quot;, &quot;{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;Web.Api&quot;, &quot;src\\Web.Api\\Web.Api.csproj&quot;, &quot;{86506D03-3746-41E7-A645-97D3633981DB}&quot;\nEndProject\nProject(&quot;{2150E333-8FDC-42A3-9474-1A3956D46DE8}&quot;) = &quot;tests&quot;, &quot;tests&quot;, &quot;{1EB88D85-BE1E-46DE-99A2-2F02363060AF}&quot;\nEndProject\nProject(&quot;{9A19103F-16F7-4668-BE54-9A1E7A4F7556}&quot;) = &quot;ArchitectureTests&quot;, &quot;tests\\ArchitectureTests\\ArchitectureTests.csproj&quot;, &quot;{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA}&quot;\nEndProject\nProject(&quot;{E53339B2-1760-4266-BCC7-CA923CBCF16C}&quot;) = &quot;docker-compose&quot;, &quot;docker-compose.dcproj&quot;, &quot;{34BB3069-D5D0-4046-ACAD-A2025ED7678F}&quot;\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{166778A2-518F-47F0-BBC7-DB49C76A963C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{166778A2-518F-47F0-BBC7-DB49C76A963C}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{166778A2-518F-47F0-BBC7-DB49C76A963C}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{166778A2-518F-47F0-BBC7-DB49C76A963C}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{86506D03-3746-41E7-A645-97D3633981DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{86506D03-3746-41E7-A645-97D3633981DB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{86506D03-3746-41E7-A645-97D3633981DB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{86506D03-3746-41E7-A645-97D3633981DB}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{34BB3069-D5D0-4046-ACAD-A2025ED7678F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{34BB3069-D5D0-4046-ACAD-A2025ED7678F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{34BB3069-D5D0-4046-ACAD-A2025ED7678F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{34BB3069-D5D0-4046-ACAD-A2025ED7678F}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{166778A2-518F-47F0-BBC7-DB49C76A963C} = {64A28C1B-09AF-426E-8721-D002BE554B48}\n\t\t{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B} = {64A28C1B-09AF-426E-8721-D002BE554B48}\n\t\t{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7} = {64A28C1B-09AF-426E-8721-D002BE554B48}\n\t\t{C699FD09-4D82-4C4B-8744-4FD3B0D60EFC} = {64A28C1B-09AF-426E-8721-D002BE554B48}\n\t\t{86506D03-3746-41E7-A645-97D3633981DB} = {64A28C1B-09AF-426E-8721-D002BE554B48}\n\t\t{8D8E2A8A-D3FE-4230-BEF7-C427D6BD87DA} = {1EB88D85-BE1E-46DE-99A2-2F02363060AF}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {B948A3CC-9872-4612-ABD2-BB3D49671542}\n\tEndGlobalSection\nEndGlobal\n</code></pre>\n<p>Good luck trying to make sense of that during a merge conflict!</p>\n<h2>What <code>.slnx</code> looks like</h2>\n<p>A minimal <code>.slnx</code> is basically a list of projects in XML.</p>\n<p>Here's the same solution as above, but in <code>.slnx</code> format.\nWe even have solution items, folders, and a docker-compose project.</p>\n<pre><code class=\"language-xml\">&lt;Solution&gt;\n  &lt;Folder Name=&quot;/Solution Items/&quot;&gt;\n    &lt;File Path=&quot;.editorconfig&quot; /&gt;\n    &lt;File Path=&quot;Directory.Build.props&quot; /&gt;\n    &lt;File Path=&quot;Directory.Packages.props&quot; /&gt;\n  &lt;/Folder&gt;\n  &lt;Folder Name=&quot;/src/&quot;&gt;\n    &lt;Project Path=&quot;src/Application/Application.csproj&quot; /&gt;\n    &lt;Project Path=&quot;src/Domain/Domain.csproj&quot; /&gt;\n    &lt;Project Path=&quot;src/Infrastructure/Infrastructure.csproj&quot; /&gt;\n    &lt;Project Path=&quot;src/SharedKernel/SharedKernel.csproj&quot; /&gt;\n    &lt;Project Path=&quot;src/Web.Api/Web.Api.csproj&quot; /&gt;\n  &lt;/Folder&gt;\n  &lt;Folder Name=&quot;/tests/&quot;&gt;\n    &lt;Project Path=&quot;tests/ArchitectureTests/ArchitectureTests.csproj&quot; /&gt;\n  &lt;/Folder&gt;\n  &lt;Project Path=&quot;docker-compose.dcproj&quot;&gt;\n    &lt;Build /&gt;\n  &lt;/Project&gt;\n&lt;/Solution&gt;\n\n</code></pre>\n<p>It looks remarkably similar to a <code>.csproj</code> file.</p>\n<h2>How to Migrate Today</h2>\n<p>The <code>.slnx</code> format is available in recent versions of Visual Studio 2022 (v17.13+) and the .NET 9 SDK. Here is how you can switch.</p>\n<p><strong>Option 1: The Command Line</strong></p>\n<p>If you have the .NET 9 SDK installed (specifically 9.0.200 or later), you can migrate instantly via the CLI.</p>\n<ol>\n<li>\n<p>Open your terminal in the solution folder.</p>\n</li>\n<li>\n<p>Run the migration command:</p>\n<pre><code class=\"language-bash\">dotnet sln migrate\n</code></pre>\n</li>\n<li>\n<p>This creates a new <code>.slnx</code> file alongside your old <code>.sln</code>.</p>\n</li>\n</ol>\n<p>At this point I would recommend deleting the old <code>.sln</code> file to avoid confusion.\nThere's no point in keeping both in the same repo.</p>\n<p><strong>Option 2: Visual Studio &quot;Save As&quot;</strong></p>\n<p>If you prefer the GUI, you can do this directly inside Visual Studio 2022 (or 2026).</p>\n<ol>\n<li>\n<p>Select the Solution in the Solution Explorer.</p>\n</li>\n<li>\n<p>Go to <code>File</code> &gt; <code>Save Solution As...</code>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_172/visual_studio_save_as.png\" alt=\"The file menu in Visual Studio with the Save As option highlighted.\">\n</li>\n<li>\n<p>Change the &quot;Save as type&quot; dropdown to Xml Solution File (*.slnx).</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_172/save_as_xml_solution_file.png\" alt=\"The Save As dialog in Visual Studio with the Xml Solution File option selected.\">\n</li>\n</ol>\n<h2>Why You Should Care</h2>\n<ul>\n<li><strong>Fewer Merge Conflicts</strong>: The #1 benefit.\nBecause the file is simple XML without random GUIDs changing, git merges become trivial.</li>\n<li><strong>Human Readable</strong>: You can open this in Notepad, understand it, and edit it without breaking your entire build.</li>\n<li><strong>Consistency</strong>: It finally aligns the solution format with the project format (<code>.csproj</code>), which moved to simplified XML years ago.</li>\n<li><strong>Performance</strong>: Smaller file sizes and simpler parsing mean slightly faster load times for massive solutions.</li>\n</ul>\n<h2>Is it Ready?</h2>\n<p>As of late 2025/early 2026, <code>.slnx</code> is technically a <strong>Preview</strong> feature.</p>\n<p><strong>Safe to use</strong>?\nYes, the format is stable.</p>\n<p><strong>Tooling support</strong>?\nVisual Studio 2022, Visual Studio 2026 and Rider support it well.\nSo does the .NET CLI.\nSome older CI/CD pipelines or 3rd party tools might not recognize the extension yet.</p>\n<p><strong>My recommendation</strong>: Try it on a side project or a branch first.\nIf your CI pipeline passes, you are ready to modernize.</p>\n<p><strong>I'm using this format</strong> in all my new projects and have migrated several existing ones without issues.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-new-slnx-solution-format-migration-guide",
            "title": "The New .slnx Solution Format (migration guide)",
            "summary": "See what changes in .slnx, how to convert your existing .sln, and what to watch out for in CI.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_172.png",
            "date_modified": "2025-12-13T00:00:00.000Z",
            "date_published": "2025-12-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way",
            "content_html": "<p><code>DbContext</code> is not thread-safe, because it tracks changes and wraps a single database connection.\nRunning two queries on it at the same time throws &quot;A second operation started on this context&quot;.\nInject <code>IDbContextFactory&lt;T&gt;</code> and create a fresh context inside each parallel task, so every query gets its own connection.</p>\n<p>We have all built <em>that</em> endpoint.</p>\n<p>You know the one: the &quot;Executive Dashboard&quot; or the &quot;User Summary&quot; screen.\nIt's the endpoint that needs to fetch three or four completely unrelated sets of data to paint a complete picture for the user.\nIt needs the last 50 orders, the current system health logs, the user's profile settings, and maybe a notification count.</p>\n<p>So, you write the code the standard way:</p>\n<pre><code class=\"language-csharp\">var orders = await GetRecentOrdersAsync(userId);\nvar logs = await GetSystemLogsAsync();\nvar stats = await GetUserStatsAsync(userId);\n\nreturn new DashboardDto(orders, logs, stats);\n</code></pre>\n<p>This works.\nIt's clean.\nIt's readable.\nBut there is a problem.</p>\n<p>If <code>GetRecentOrdersAsync</code> takes 300ms, <code>GetSystemLogsAsync</code> takes 400ms, and <code>GetUserStatsAsync</code> takes 300ms,\nyour users are staring at a loading spinner for 1 full second (300 + 400 + 300).</p>\n<p>In a distributed system, latency kills user experience.\nSince these data sets are unrelated, we should be able to run them in parallel.\nIf we did, the total time would only be the duration of the slowest query (400ms).\nThat is a 60% performance improvement just by changing how we execute the code.</p>\n<p>But if you try the naive approach with Entity Framework Core, your application will crash.</p>\n<h2>The False Promise of Task.WhenAll</h2>\n<p>The most common mistake developers make when trying to optimize this is wrapping their existing repository calls in tasks and waiting for them all at once.</p>\n<p>It looks something like this:</p>\n<pre><code class=\"language-csharp\">// ❌ DO NOT DO THIS\npublic async Task&lt;DashboardData&gt; GetDashboardData(int userId)\n{\n    // These methods all use the same injected _dbContext\n    var ordersTask = _repository.GetOrdersAsync(userId);\n    var logsTask = _repository.GetLogsAsync();\n    var statsTask = _repository.GetStatsAsync(userId);\n\n    await Task.WhenAll(ordersTask, logsTask, statsTask); // BOOM 💥\n\n    return new DashboardData(ordersTask.Result, logsTask.Result, statsTask.Result);\n}\n</code></pre>\n<p>If you run this, you will immediately hit <a href=\"https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#avoiding-dbcontext-threading-issues\">this dreaded exception</a>:</p>\n<blockquote>\n<p>A second operation started on this context before a previous operation completed.\nThis is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.</p>\n</blockquote>\n<p><strong>Why does this happen?</strong></p>\n<p>The <code>DbContext</code> in EF Core is <strong>not thread-safe</strong>.\nIt is a stateful object designed to manage a single unit of work.\nIt maintains a &quot;Change Tracker&quot; to keep track of the entities you've loaded, and it wraps a single underlying database connection.</p>\n<p>Database protocols (like the TCP stream for PostgreSQL or SQL Server) are generally synchronous at the connection level.\nYou cannot push two different SQL queries down the same wire at the exact same millisecond.\nWhen you use <code>Task.WhenAll</code>, multiple threads try to grab that single connection simultaneously,\nand EF Core steps in to throw the exception to prevent data corruption.</p>\n<p>So, we have a dilemma: We want the speed of parallelism, but the <code>DbContext</code> forces us into sequential execution.</p>\n<h2>The Solution</h2>\n<p>Since .NET 5, EF Core has provided a first-class solution for this exact scenario: <code>IDbContextFactory&lt;T&gt;</code>.</p>\n<p>Instead of injecting a scoped instance of your context (which lives for the entire HTTP request),\nyou inject a factory that allows you to create lightweight, independent instances of <code>DbContext</code> on demand.</p>\n<p><strong><em>Note</em></strong>: While using the factory is the cleanest approach for Dependency Injection,\nyou can also manually instantiate the context (<code>using var context = new AppDbContext(options)</code>) if you have access to the <code>DbContextOptions</code>.</p>\n<p>First, we need to register the factory in our <code>Program.cs</code>.</p>\n<pre><code class=\"language-csharp\">// This registers IDbContextFactory&lt;AppDbContext&gt; as a Singleton (by default)\n// It also registers AppDbContext as Scoped for ease of use elsewhere\nbuilder.Services.AddDbContextFactory&lt;AppDbContext&gt;(options =&gt;\n{\n    options.UseNpgsql(builder.Configuration.GetConnectionString(&quot;db&quot;));\n});\n</code></pre>\n<p>Now, let's refactor our slow dashboard endpoint.\nInstead of injecting <code>AppDbContext</code>, we inject <code>IDbContextFactory&lt;AppDbContext&gt;</code>.</p>\n<p>Inside our method, we spin up a dedicated task for each query.\nInside each task, we create a brand new context, execute the query, and then immediately dispose of it.</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\n\npublic class DashboardService(IDbContextFactory&lt;AppDbContext&gt; contextFactory)\n{\n    public async Task&lt;DashboardDto&gt; GetDashboardAsync(int userId)\n    {\n        // 1. Start the tasks (The queries start executing immediately upon invocation)\n        var ordersTask = GetOrdersAsync(userId);\n        var logsTask = GetSystemLogsAsync();\n        var statsTask = GetUserStatsAsync(userId);\n\n        // 2. Wait for all to complete\n        await Task.WhenAll(ordersTask, logsTask, statsTask);\n\n        // 3. Return results (using 'await Task.WhenAll' here unwraps the result cleanly)\n        return new DashboardDto(\n            await ordersTask,\n            await logsTask,\n            await statsTask\n        );\n    }\n\n    private async Task&lt;List&lt;Order&gt;&gt; GetOrdersAsync(int userId)\n    {\n        // Create a fresh context for this specific operation\n        await using var context = await contextFactory.CreateDbContextAsync();\n\n        return await context.Orders\n            .AsNoTracking()\n            .Where(o =&gt; o.UserId == userId)\n            .OrderByDescending(o =&gt; o.CreatedAt)\n            .ThenByDescending(o =&gt; o.Amount)\n            .Take(50)\n            .ToListAsync();\n    }\n\n    private async Task&lt;List&lt;SystemLog&gt;&gt; GetSystemLogsAsync()\n    {\n        await using var context = await contextFactory.CreateDbContextAsync();\n\n        return await context.SystemLogs\n            .AsNoTracking()\n            .OrderByDescending(l =&gt; l.Timestamp)\n            .Take(50)\n            .ToListAsync();\n    }\n\n    private async Task&lt;UserStats?&gt; GetUserStatsAsync(int userId)\n    {\n        await using var context = await contextFactory.CreateDbContextAsync();\n\n        return await context.Users\n            .Where(u =&gt; u.Id == userId)\n            .Select(u =&gt; new UserStats { OrderCount = u.Orders.Count })\n            .FirstOrDefaultAsync();\n    }\n}\n</code></pre>\n<p><strong>Key Concepts</strong></p>\n<ol>\n<li><strong>Isolation</strong>: Each task gets its own DbContext.\nThis means they get their own database connection.\nThere is no contention.</li>\n<li><strong>Disposal</strong>: Notice the await using.\nThis is critical.\nAs soon as the query is done, we want to dispose of that context and return the connection to the pool.</li>\n</ol>\n<h2>The Benchmark</h2>\n<p>To prove this works, I built a small .NET 10 app using Aspire and PostgreSQL..\nSince I'm running this locally, the absolute times are very low.\nIf I used a remote database, the times would be higher, but the speedup ratio would be similar.</p>\n<p><strong>Sequential Execution</strong>: ~36ms</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_171/sequential_ef_queries_trace.png\" alt=\"A distributed trace showing sequential EF Core queries.\">\n<p>The waterfall is painfully obvious here.\nEach operation waits for the previous one to finish.</p>\n<p><strong>Parallel Execution</strong>: ~13ms</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_171/parallel_ef_queries_trace.png\" alt=\"A distributed trace showing parallel EF Core queries.\">\n<p>By using the parallel approach, the timeline compresses.\nAll three database spans start at the same time and complete together.</p>\n<h2>Trade-offs &amp; Conclusion</h2>\n<p><code>IDbContextFactory</code> bridges the gap between EF Core's unit-of-work design and the reality of modern, parallel requirements.\nIt allows you to break out of the &quot;one request, one thread&quot; box without sacrificing safety.</p>\n<p>However, use this pattern sparingly:</p>\n<ul>\n<li><strong>Connection pool starvation</strong>: A single HTTP request now occupies 3 database connections simultaneously instead of 1.\nIf you have high concurrency, you can easily exhaust your connection pool.</li>\n<li><strong>Context overhead</strong>: If your queries are extremely fast (e.g., simple lookups by ID),\nthe overhead of creating multiple contexts and tasks might make the parallel version slower than the sequential one.</li>\n</ul>\n<p>Next time you are staring at a slow dashboard, don't reach for raw SQL immediately.\nCheck your awaits.\nIf they are lined up single-file, it might be time to introduce some parallelization.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way",
            "title": "DbContext is Not Thread-Safe: Parallelizing EF Core Queries the Right Way",
            "summary": "Learn how to safely parallelize EF Core queries to improve performance by using IDbContextFactory to create isolated contexts, avoiding the thread-safety…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_171.png",
            "date_modified": "2025-12-06T00:00:00.000Z",
            "date_published": "2025-12-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/vertical-slice-architecture-where-does-the-shared-logic-live",
            "content_html": "<p>Shared code in Vertical Slice Architecture belongs in three tiers.\nTechnical infrastructure (logging, database contexts, the Result pattern) is shared freely, domain rules get pushed down into entities and value objects, and logic used by two related slices stays in a <code>Shared</code> folder inside that feature.\nA <code>Common</code> project couples unrelated features through the same helpers.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>Vertical Slice Architecture</strong></a> (VSA) seems like a breath of fresh air when you first encounter it.\nYou stop jumping between seven layers to add a single field.\nYou delete the dozens of projects in your solution.\nYou feel liberated.</p>\n<p>But when you start implementing more complex features, the cracks begin to show.</p>\n<p>You build a <code>CreateOrder</code> slice.\nThen <code>UpdateOrder</code>.\nThen <code>GetOrder</code>.\nSuddenly, you notice the repetition.\nThe address validation logic is in three places.\nThe pricing algorithm is needed by both Cart and Checkout.</p>\n<p>You feel the urge to create a <code>Common</code> project or <code>SharedServices</code> folder.\nThis is the most critical moment in your VSA adoption.</p>\n<p>Choose wrong, and you'll reintroduce the coupling you were trying to escape.\nChoose right, and you maintain the independence that makes VSA worthwhile.</p>\n<p>Here's how I approach <strong>shared code</strong> in <strong>Vertical Slice Architecture</strong>.</p>\n<h2>The Guardrails vs. The Open Road</h2>\n<p>To understand why this is hard, we need to look at what we left behind.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/clean-architecture-the-missing-chapter\"><strong>Clean Architecture</strong></a> provides strict guardrails.\nIt tells you exactly where code lives: Entities go in Domain, interfaces go in Application, implementations go in Infrastructure.\nIt's safe.\nIt prevents mistakes, but it also prevents shortcuts when they're appropriate.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\"><strong>Vertical Slice Architecture</strong></a> removes the guardrails.\nIt says, &quot;Organize code by feature, not technical concern&quot;.\nThis gives you speed and flexibility, but it shifts the burden of discipline onto <em>you</em>.</p>\n<p>So what can you do about it?</p>\n<h2>The Trap: The &quot;Common&quot; Junk Drawer</h2>\n<p>The path of least resistance is to create a project (or folder) named <code>Shared</code>, <code>Common</code>, or <code>Utils</code>.</p>\n<p>This is almost always a mistake.</p>\n<p>Imagine a <code>Common.Services</code> project with an <code>OrderCalculationService</code> class.\nIt has a method for cart totals (used by Cart), another for historical revenue (used by Reporting),\nand a helper for invoice formatting (used by Invoices).\nThree unrelated concerns.\nThree different change frequencies.\nOne class coupling them all together.</p>\n<p>A <code>Common</code> project inevitably becomes a junk drawer for anything you can't be bothered to name properly.\nIt creates a tangled web of dependencies where unrelated features are coupled together because they happen to use the same helper method.</p>\n<p>You've reintroduced the very coupling you tried to escape.</p>\n<h2>The Decision Framework</h2>\n<p>When I hit a potential sharing situation, I ask three questions:</p>\n<p><strong>1. Is this infrastructural or domain?</strong></p>\n<p>Infrastructure (database contexts, logging, HTTP clients) almost always gets shared. Domain concepts need more scrutiny.</p>\n<p><strong>2. How stable is this concept?</strong></p>\n<p>If it changes once a year, share it. If it changes with every feature request, keep it local.</p>\n<p><strong>3. Am I past the &quot;Rule of Three&quot;?</strong></p>\n<p>Duplicating the same code once is fine.\nHowever, creating three duplicates should raise an eyebrow.\nDon't abstract until you hit three.</p>\n<p>We solve this by refactoring our code.\nLet's look at some examples.</p>\n<h2>The Three Tiers of Sharing</h2>\n<p>Instead of binary &quot;Shared vs. Not Shared,&quot; think in three tiers.</p>\n<h3>Tier 1: Technical Infrastructure (Share Freely)</h3>\n<p>Pure plumbing that affects all slices equally: logging adapters, database connection factories, auth middleware,\nthe <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>Result pattern</strong></a>, <a href=\"https://milanjovanovic.tech/blog/validation-vertical-slice-architecture\"><strong>validation pipelines</strong></a>.</p>\n<p>Centralize this in a <code>Shared.Kernel</code> or <code>Infrastructure</code> project.\nNote that this can also be a folder within your solution.\nIt rarely changes due to business requirements.</p>\n<pre><code class=\"language-csharp\">// ✅ Good Sharing: Technical Kernel\npublic readonly record struct Result\n{\n    public bool IsSuccess { get; }\n    public string Error { get; }\n\n    private Result(bool isSuccess, string error)\n    {\n        IsSuccess = isSuccess;\n        Error = error;\n    }\n\n    public static Result Success() =&gt; new(true, string.Empty);\n    public static Result Failure(string error) =&gt; new(false, error);\n}\n</code></pre>\n<h3>Tier 2: Domain Concepts (Share and Push Logic Down)</h3>\n<p>This is one of the best places to share logic.\nInstead of scattering business rules across slices, push them into entities and value objects.</p>\n<p>Here's an example:</p>\n<pre><code class=\"language-csharp\">// ✅ Good Sharing: Entity with Business Logic\npublic class Order\n{\n    public Guid Id { get; private set; }\n    public OrderStatus Status { get; private set; }\n    public List&lt;OrderLine&gt; Lines { get; private set; }\n\n    public bool CanBeCancelled() =&gt; Status == OrderStatus.Pending;\n\n    public Result Cancel()\n    {\n        if (!CanBeCancelled())\n        {\n            return Result.Failure(&quot;Only pending orders can be cancelled.&quot;);\n        }\n\n        Status = OrderStatus.Cancelled;\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>Now <code>CancelOrder</code>, <code>GetOrder</code>, and <code>UpdateOrder</code> all use the same business rules.\nThe logic lives in one place.</p>\n<div className=\"note-panel\">\n  This implies an important concept: **different vertical slices can share the\n  same domain model.**\n</div>\n<h3>Tier 3: Feature-Specific Logic (Keep It Local)</h3>\n<p>Logic shared between related slices, like <code>CreateOrder</code> and <code>UpdateOrder</code>, doesn't need to go global.\nCreate a <code>Shared</code> folder (there's an exception to every rule) within the feature:</p>\n<pre><code>📂 Features\n└──📂 Orders\n    ├──📂 CreateOrder\n    ├──📂 UpdateOrder\n    ├──📂 GetOrder\n    └──📂 Shared\n        ├──📄 OrderValidator.cs\n        └──📄 OrderPricingService.cs\n</code></pre>\n<p>This also has a hiddene benefit.\nIf you delete the Orders feature, the shared logic goes with it.\nNo zombie code left behind.</p>\n<p>Let's explore some advanced scenarios most people overlook.</p>\n<h2>Cross-Feature Sharing</h2>\n<p>What about <strong>sharing code between unrelated features</strong> in Vertical Slice Architecture?</p>\n<p>The <code>CreateOrder</code> slice needs to check if a customer exists.\n<code>GenerateInvoice</code> needs to calculate tax.\nOrders and Customers both need to format notification messages.</p>\n<p>This doesn't fit neatly into a feature's <code>Shared</code> folder.\nSo where does it go?</p>\n<p><strong>First, ask: do you actually need to share?</strong></p>\n<p>Most cross-feature &quot;sharing&quot; is just data access in disguise.</p>\n<p>If <code>CreateOrder</code> needs customer data, it queries the database directly.\nIt doesn't call into the Customers feature.\nEach slice owns its data access.\nThe <code>Customer</code> entity is shared (it lives in <code>Domain</code>), but there's no shared service between them.</p>\n<p><strong>When you genuinely need shared logic</strong>, ask what it <em>is</em>:</p>\n<ul>\n<li><strong>Domain logic</strong> (business rules, calculations) → <code>Domain/Services</code></li>\n<li><strong>Infrastructure</strong> (external APIs, formatting) → <code>Infrastructure/Services</code></li>\n</ul>\n<pre><code class=\"language-csharp\">// Domain/Services/TaxCalculator.cs\npublic class TaxCalculator\n{\n    public decimal CalculateTax(Address address, decimal subtotal)\n    {\n        var rate = GetTaxRate(address.State, address.Country);\n        return subtotal * rate;\n    }\n}\n</code></pre>\n<p>Both <code>CreateOrder</code> and <code>GenerateInvoice</code> can use it without coupling to each other.</p>\n<p>Before creating any cross-feature service, ask: could this logic live on a domain entity instead?\nMost &quot;shared business logic&quot; is actually data access, domain logic that belongs on an entity, or premature abstraction.</p>\n<p>If you need to trigger a side effect in another feature, I recommend using <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>messaging and events</strong></a>.\nAlternatively, the feature you want to call into can explore a facade (<a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>public API</strong></a>) for that operation.</p>\n<h2>When Duplication Is the Right Call</h2>\n<p>Sometimes &quot;shared&quot; code isn't actually shared.\nIt just looks that way.</p>\n<pre><code class=\"language-csharp\">// Features/Orders/GetOrder\npublic record GetOrderResponse(Guid Id, decimal Total, string Status);\n\n// Features/Orders/CreateOrder\npublic record CreateOrderResponse(Guid Id, decimal Total, string Status);\n</code></pre>\n<p>They're identical.\nThe temptation to create a <code>SharedOrderDto</code> is overwhelming.\nResist it.</p>\n<p>Next week, <code>GetOrder</code> needs a tracking URL.\nBut <code>CreateOrder</code> happens before shipping, so there's no URL yet.\nIf you'd shared the DTO, you'd now have a nullable property that's confusingly empty half the time.</p>\n<p><strong>Duplication is cheaper than the wrong abstraction.</strong></p>\n<h2>The Practical Structure</h2>\n<p>Here's what a <a href=\"https://milanjovanovic.tech/blog/vertical-slice-project-structure-dotnet\"><strong>mature Vertical Slice Architecture project</strong></a> looks like:</p>\n<pre><code>📂 src\n└──📂 Features\n│   ├──📂 Orders\n│   │   ├──📂 CreateOrder\n│   │   ├──📂 UpdateOrder\n│   │   └──📂 Shared          # Order-specific sharing\n│   ├──📂 Customers\n│   │   ├──📂 GetCustomer\n│   │   └──📂 Shared          # Customer-specific sharing\n│   └──📂 Invoices\n│       └──📂 GenerateInvoice\n└──📂 Domain\n│   ├──📂 Entities\n│   ├──📂 ValueObjects\n│   └──📂 Services            # Cross-feature domain logic\n└──📂 Infrastructure\n│   ├──📂 Persistence\n│   └──📂 Services\n└──📂 Shared\n    └──📂 Behaviors\n</code></pre>\n<ul>\n<li><strong>Features</strong> — Self-contained slices. Each owns its request/response models.</li>\n<li><strong>Features/[Name]/Shared</strong> — Local sharing between related slices.</li>\n<li><strong>Domain</strong> — Entities, value objects, and domain services. Shared business logic lives here.</li>\n<li><strong>Infrastructure</strong> — Technical concerns.</li>\n<li><strong>Shared</strong> — Cross-cutting behaviors only.</li>\n</ul>\n<h2>The Rules</h2>\n<p>After building several systems this way, here's what I've landed on:</p>\n<ol>\n<li>\n<p><strong>Features own their request/response models.</strong> No exceptions.</p>\n</li>\n<li>\n<p><strong>Push business logic into the domain.</strong> Entities and value objects are the best place to share business rules.</p>\n</li>\n<li>\n<p><strong>Keep feature-family sharing local.</strong> If only Order slices need it, keep it in <code>Features/Orders/Shared</code> (feel free to find a better name than <code>Shared</code>).</p>\n</li>\n<li>\n<p><strong>Infrastructure is shared by default</strong>. Database contexts, HTTP clients, logging. These are technical concerns.</p>\n</li>\n<li>\n<p><strong>Apply the Rule of Three.</strong> Don't extract until you have three real usages with identical, stable logic.</p>\n</li>\n</ol>\n<h2>Takeaway</h2>\n<p>Vertical Slice Architecture asks: &quot;What feature does <em>this</em> belong to?&quot;</p>\n<p>The shared code question is really asking: &quot;What do I do when the answer is <em>multiple features</em>?&quot;</p>\n<p>Acknowledge that some concepts genuinely span features.\nGive them a home based on their <em>nature</em> (domain, infrastructure, or cross-cutting behavior).\nResist the urge to share everything just because you could.</p>\n<p>The goal isn't zero duplication.\nIt's code that's easy to change when requirements change.</p>\n<p>And requirements always change.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/vertical-slice-architecture-where-does-the-shared-logic-live",
            "title": "Vertical Slice Architecture: Where Does the Shared Logic Live?",
            "summary": "Deciding where shared logic lives is the most critical moment in Vertical Slice Architecture adoption, because choosing wrong reintroduces the coupling you…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_170.png",
            "date_modified": "2025-11-29T00:00:00.000Z",
            "date_published": "2025-11-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-false-comfort-of-the-happy-path-decoupling-your-services",
            "content_html": "<p>Calling email and analytics APIs directly from your registration method makes the user wait on them, and a failure halfway through leaves partial state.\nDispatch a domain event instead, write it to an outbox table in the same transaction, and let a background worker publish it.\nWhen the follow-up step is mandatory, use a Saga with compensating transactions.</p>\n<p><strong>Let's be honest: We've all written this code.</strong></p>\n<p>It's Monday morning, you have a deadline, and you need to implement a user registration feature.\nIt's simple enough: save the user, send a welcome email, and track the signup in your analytics dashboard.</p>\n<p>You write this:</p>\n<pre><code class=\"language-csharp\">public class UserService(\n        IUserRepository userRepository,\n        IEmailService emailService,\n        IAnalyticsService analyticsService)\n{\n    public async Task RegisterUser(string email, string password)\n    {\n        var user = new User(email, password);\n        await userRepository.SaveAsync(user);\n\n        // 1. Directly coupled to email service (external API)\n        await emailService.SendWelcomeEmail(user.Email);\n\n        // 2. Directly coupled to analytics (this could be an external API)\n        await analyticsService.TrackUserRegistration(user.Id);\n\n        // What if we need to add more features?\n        // This method will keep growing...\n    }\n}\n</code></pre>\n<p>It looks clean. It's readable. It works on your machine.</p>\n<p>But this method is a <strong>ticking time bomb</strong>.</p>\n<p>It assumes the &quot;Happy Path&quot; is the <em>only</em> path.\nIt assumes the network is reliable, the email provider is up, and the analytics API is fast.\nIn production, none of these are guaranteed.</p>\n<p>Thinking further, I'm sure you can imagine similar code in your own projects.\nIt might not be this exact scenario, but the pattern is common: a single method that orchestrates multiple side effects in a linear fashion.</p>\n<p>Let's break down why this code is dangerous and how we can refactor it into a robust, event-driven architecture.</p>\n<h2>The Hidden Dangers of the &quot;God Method&quot;</h2>\n<p>There are three major issues hiding in those ten lines of code.</p>\n<h3>1. Temporal Coupling (Latency)</h3>\n<p>When a user clicks &quot;Register,&quot; they have to wait for:</p>\n<ol>\n<li>The Database <strong>+</strong></li>\n<li>The SMTP Server <strong>+</strong></li>\n<li>The Analytics API</li>\n</ol>\n<p>If your analytics provider is having a bad day and takes 3 seconds to respond, <strong>your user waits 3 seconds</strong>.\nYou are punishing your user for the slowness of a background system they don't even care about.</p>\n<h3>2. The Partial Failure State</h3>\n<p>This is the <strong>most critical risk</strong>. Imagine this scenario:</p>\n<ol>\n<li><code>SaveAsync(user)</code> succeeds. The user is in the DB.</li>\n<li><code>SendWelcomeEmail</code> succeeds. The user gets an email.</li>\n<li><code>TrackUserRegistration</code> throws a <code>503 Service Unavailable</code>.</li>\n</ol>\n<p><strong>What happens now?</strong>\nIf you wrap this in a transaction and rollback, you have deleted the user from the DB... <strong>but you already sent them a welcome email.</strong>\nThe user tries to log in, but they don't exist.\nNow what?</p>\n<p>If you <em>don't</em> rollback, you have a user in your system that is missing from your analytics.\nYou have data inconsistency.</p>\n<h3>3. Violation of Single Responsibility (SRP)</h3>\n<p>You might argue that because we are using interfaces (<code>IEmailService</code>), we are decoupled.\nThat is true for <em>implementation details</em>, but false for <em>orchestration</em>.</p>\n<p>The <code>UserService</code> currently has two reasons to change:</p>\n<ol>\n<li><strong>Core Domain Logic:</strong> &quot;We now require a username in addition to email.&quot;</li>\n<li><strong>Notification Policy:</strong> &quot;Marketing wants to send an SMS in addition to the Email.&quot;</li>\n</ol>\n<p>The <code>UserService</code> should strictly be responsible for the <strong>state change</strong> (creating the user).\nIt should not be responsible for <strong>orchestrating the side effects</strong> of that change.</p>\n<h2>Level 1: Logical Decoupling with Domain Events</h2>\n<p>The first step to fixing this is to invert the control.\nInstead of the <code>UserService</code> <em>commanding</em> other services to do things, it should simply <em>announce</em> that something happened.</p>\n<p>We can use <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>Domain Events</strong></a> to achieve this.</p>\n<p>Here is the refactored <code>UserService</code>:</p>\n<pre><code class=\"language-csharp\">public class UserService(\n        IUserRepository userRepository,\n        IDomainEventDispatcher dispatcher,\n        IUnitOfWork unitOfWork)\n{\n    public async Task RegisterUser(string email, string password)\n    {\n        // 1. Create the User Entity\n        var user = new User(email, password);\n\n        // 2. Capture the side effect as an event object\n        var userRegisteredEvent = new UserRegisteredEvent(user.Id, user.Email);\n\n        // 3. Add the entity to the repository\n        await userRepository.AddAsync(user);\n\n        // 4. Dispatch the event (Assuming in-process dispatching here for simplicity)\n        // Note: Handlers for Email and Analytics are now completely separate classes.\n        await dispatcher.Dispatch(userRegisteredEvent);\n\n        await unitOfWork.SaveChangesAsync();\n    }\n}\n</code></pre>\n<p>The <code>UserService</code> is now stable.\nAdding a &quot;Loyalty Points&quot; feature later doesn't require touching this method.\nYou just add a new handler for the <code>UserRegisteredEvent</code>.</p>\n<p><strong>However, we haven't solved the reliability problem yet.</strong>\nIf the process crashes immediately after <code>Dispatch</code> but before <code>SaveChangesAsync</code> completes, we might send an email for a user that failed to save.\nOr, if we save first and dispatch later, we might save the user but lose the event if the server crashes.</p>\n<h2>Level 2: Reliability with the Outbox Pattern</h2>\n<p>To fix this, we need <strong>Atomicity</strong>.\nAtomicity means that a set of operations either all succeed or all fail together.</p>\n<p>We need to guarantee that if the <code>User</code> is saved, the <code>UserRegisteredEvent</code> is also saved.</p>\n<p>Enter the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox Pattern</strong></a>.</p>\n<p>Instead of publishing the event immediately to a message bus, we save the event to an <code>OutboxMessages</code> table in the <strong>same database transaction</strong> as the user.</p>\n<p>Here is the complete implementation logic:</p>\n<pre><code class=\"language-csharp\">public async Task RegisterUser(string email, string password)\n{\n    // 1. Create the Domain Event\n    var user = new User(email, password);\n    var domainEvent = new UserRegisteredEvent(user.Id, user.Email);\n\n    // 2. Open a Transaction\n    using var transaction = dbContext.Database.BeginTransaction();\n\n    try\n    {\n        // 3. Save the User to the Users Table\n        dbContext.Users.Add(user);\n\n        // 4. Serialize the Event and Save to Outbox Table\n        var outboxMessage = new OutboxMessage\n        {\n            Id = Guid.NewGuid(),\n            Type = nameof(UserRegisteredEvent),\n            Content = JsonSerializer.Serialize(domainEvent),\n            OccurredOn = DateTime.UtcNow,\n            ProcessedOn = null // Null means it hasn't been handled yet\n        };\n\n        dbContext.OutboxMessages.Add(outboxMessage);\n\n        // 5. Commit BOTH changes atomically\n        await dbContext.SaveChangesAsync();\n        await transaction.CommitAsync();\n    }\n    catch\n    {\n        await transaction.RollbackAsync();\n        throw;\n    }\n}\n</code></pre>\n<p>Now, a <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>background worker</strong></a> (running in a separate process) polls the <code>OutboxMessages</code> table.\nIt picks up the message and publishes it to your message bus (RabbitMQ, Azure Service Bus, etc.).</p>\n<p>If the email service is down, the background worker just retries later.\n<strong>We have achieved At-Least-Once delivery.</strong></p>\n<h2>Level 3: Distributed Consistency with Sagas</h2>\n<p>The Outbox pattern is perfect for side effects (fire-and-forget actions like emails).\nBut what if the subsequent action is <strong>mandatory</strong>?</p>\n<p><strong>Scenario:</strong> When a user registers, we <em>must</em> create a crypto-wallet for them in the <code>WalletService</code>.\nIf the wallet creation fails (e.g., due to regulations), we cannot allow the user to exist in our system.</p>\n<p>We can't just &quot;retry later&quot; if the <code>WalletService</code> says &quot;Fraud Detected.&quot;\nWe need to <strong>undo</strong> the user creation.</p>\n<p>This is a distributed transaction, and we handle it with the <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>Saga Pattern</strong></a>.\nA Saga coordinates a series of steps.\nIf one fails, it executes <strong>Compensating Transactions</strong> to undo the previous work.</p>\n<p>Here is how the failure scenario looks when using a <a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\"><strong>Choreography-based Saga</strong></a>:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_169/saga_sequence_diagram.png\" alt=\"A Saga Sequence Diagram showing UserService creating a user, WalletService attempting to create a wallet, failing, and UserService deleting the user as a compensation action.\">\n<p>Here's the step-by-step breakdown of the flow:</p>\n<ol>\n<li><strong>UserService:</strong> Creates User → Publishes <code>UserCreated</code></li>\n<li><strong>WalletService:</strong> Listens to <code>UserCreated</code> → Tries to create wallet\n<ul>\n<li><em>Failure:</em> Wallet creation fails</li>\n<li><em>Action:</em> Publishes <code>WalletCreationFailed</code></li>\n</ul>\n</li>\n<li><strong>UserService:</strong> Listens to <code>WalletCreationFailed</code> → <strong>Deletes/Deactivates the User</strong></li>\n</ol>\n<p>This ensures <strong>Eventual Consistency</strong>.\nThe system might be inconsistent for a few seconds (the user exists without a wallet),\nbut it will eventually settle into a valid state (the user is removed).</p>\n<h2>Summary: A Heuristic for Decision Making</h2>\n<p>You don't need Sagas for everything.\nOver-engineering is just as bad as tight coupling.\nUse this simple rule of thumb:</p>\n<ol>\n<li><strong>Is it a simple notification?</strong> (Email, Analytics, Cache Invalidation)\n<ul>\n<li><strong>Use Domain Events + Outbox.</strong> It's okay if it happens 5 seconds later.</li>\n</ul>\n</li>\n<li><strong>Is it a critical business dependency?</strong> (Payments, Inventory, Account Status)\n<ul>\n<li><strong>Use a Saga.</strong> If step B fails, step A must be reverted.</li>\n</ul>\n</li>\n</ol>\n<p>Coupling isn't just about code structure.\nIt's about understanding and managing <strong>failure boundaries</strong>.\nIf your Analytics Service goes down, it shouldn't prevent a user from registering.\nBuild your systems to survive the unhappy path.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-false-comfort-of-the-happy-path-decoupling-your-services",
            "title": "The False Comfort of the \"Happy Path\": Decoupling Your Services",
            "summary": "Coupling isn't just about code structure; it's about failure boundaries. Discover how to ensure your critical business logic survives when external…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_169.png",
            "date_modified": "2025-11-22T00:00:00.000Z",
            "date_published": "2025-11-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/exploring-csharp-file-based-apps-in-dotnet-10",
            "content_html": "<p>.NET 10 introduced file-based apps: a single <code>.cs</code> file you run with <code>dotnet run app.cs</code>, with no project or solution file.\nDirectives that start with <code>#:</code> reference NuGet packages, target an SDK, and set project properties from inside that file.\nWhen a script outgrows one file, <code>dotnet project convert</code> turns it into a full project.</p>\n<p>C# has always been a bit ceremony-heavy, and we all know it.\nEven the simplest &quot;Hello World&quot; program traditionally needed a solution file, a project file,\nand enough boilerplate to make you wonder if you should've just used a scripting language instead.</p>\n<p>Well, Microsoft finally heard us.\nWith .NET 10, they've introduced <a href=\"https://milanjovanovic.tech/blog/run-csharp-scripts-with-dotnet-run-app-no-project-files-needed\"><strong>file-based apps</strong></a>.\nAnd honestly, it's about time.</p>\n<h2>What are File-based Apps?</h2>\n<p>The idea is simple: write your C# code in a single <code>.cs</code> file and run it directly. That's it.</p>\n<p>No need to set up a whole project structure for that quick utility script you need to parse some CSV files or test an API endpoint.</p>\n<p>You get to keep everything that makes C# great: the <strong>type safety</strong>, the <strong>performance</strong>, the <strong>rich standard library</strong>.\nBut now you can use it for those throwaway scripts where setting up a full project would've been overkill.\nAnd yes, you can still <strong>reference NuGet packages</strong>, <strong>reference other C# projects</strong>, target specific SDKs,\nand configure project properties, all from within that single file using special directives that start with <code>#:</code>.</p>\n<p>The feature builds on <strong>top-level statements</strong> (remember those from C# 9?) and takes them to their logical conclusion.\nIf we're already letting people skip the class and <code>Main</code> method ceremony, why not let them skip the project ceremony too?</p>\n<h2>Getting Started with File-based Apps</h2>\n<p>Let's say you want to quickly check what day of the week a specific date falls on.\nCreate a file called <code>date-checker.cs</code>:</p>\n<pre><code class=\"language-csharp\">var targetDate = new DateTime(2025, 12, 31);\nConsole.WriteLine($&quot;New Year's 2025 falls on a {targetDate.DayOfWeek}&quot;);\nConsole.WriteLine($&quot;That's {(targetDate - DateTime.Today).Days} days from now&quot;);\n</code></pre>\n<p>Run it with:</p>\n<pre><code class=\"language-bash\">dotnet run date-checker.cs\n</code></pre>\n<p>The first time you run this, the CLI does some behind-the-scenes magic.\nIt creates a virtual project, compiles your code, and caches everything.\nSubsequent runs are nearly instant because it's smart enough to know when nothing has changed.</p>\n<h2>Real-world Example: Quick Data Processing</h2>\n<p>Here's where things get interesting.\nSay you need to quickly process some JSON data and generate a report.</p>\n<p>Let's do something practical with <code>System.Text.Json</code> and <code>CsvHelper</code>:</p>\n<pre><code class=\"language-csharp\">#:package CsvHelper@33.1.0\nusing System.Text.Json;\nusing CsvHelper;\nusing System.Globalization;\n\nvar json = await File.ReadAllTextAsync(&quot;sales_data.json&quot;);\nvar sales = JsonSerializer.Deserialize&lt;List&lt;SaleRecord&gt;&gt;(json);\n\nvar topProducts = sales\n    .GroupBy(s =&gt; s.Product)\n    .Select(g =&gt; new {\n        Product = g.Key,\n        TotalRevenue = g.Sum(s =&gt; s.Amount),\n        UnitsSold = g.Count()\n    })\n    .OrderByDescending(p =&gt; p.TotalRevenue)\n    .Take(10);\n\nusing var writer = new StreamWriter(&quot;top_products.csv&quot;);\nusing var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);\ncsv.WriteRecords(topProducts);\n\nConsole.WriteLine(&quot;Report generated! Check top_products.csv&quot;);\n\nrecord SaleRecord(string Product, decimal Amount, DateTime Date);\n</code></pre>\n<p>Notice how we're mixing package references, async operations, LINQ, and record types, all in a single file that reads like a cohesive script.\nThis is the kind of thing you'd typically reach for Python for, but now you can stay in C# land.</p>\n<h2>Building Something More Ambitious with Aspire</h2>\n<p>You can actually create an <strong>Aspire AppHost</strong> in a single file, which is pretty wild when you think about it:</p>\n<pre><code class=\"language-csharp\">#:sdk Aspire.AppHost.Sdk@13.0.0\n#:package Aspire.Hosting.AppHost@13.0.0\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nvar cache = builder.AddRedis(&quot;cache&quot;)\n    .WithDataVolume();\n\nvar postgres = builder.AddPostgres(&quot;postgres&quot;)\n    .WithDataVolume()\n    .AddDatabase(&quot;tododb&quot;);\n\nvar todoApi = builder.AddProject&lt;Projects.TodoApi&gt;(&quot;api&quot;)\n    .WithReference(cache)\n    .WithReference(postgres);\n\nbuilder.AddNpmApp(&quot;frontend&quot;, &quot;../TodoApp&quot;)\n    .WithReference(todoApi)\n    .WithReference(&quot;api&quot;)\n    .WithHttpEndpoint(env: &quot;PORT&quot;)\n    .WithExternalHttpEndpoints();\n\nbuilder.Build().Run();\n</code></pre>\n<p>With the <code>#:sdk Aspire.AppHost.Sdk@13.0.0</code> directive, your single file becomes a full orchestrator for a distributed application.\nYou're defining infrastructure, wiring up dependencies, and setting up a complete development environment, all without creating a project file.\nIt's particularly useful when you're prototyping architectures or need to quickly spin up a test environment.</p>\n<h2>Migrating to a Full Project</h2>\n<p>Eventually, some scripts outgrow their single-file roots.\nMaybe you need to split things into multiple files, or maybe you want proper IDE support for debugging.\nThe transition is painless:</p>\n<pre><code class=\"language-bash\">dotnet project convert MyUtility.cs\n</code></pre>\n<p>This generates a proper project structure while preserving all your package references and SDK choices.\nYour code moves to <code>Program.cs</code>, and you get a <code>.csproj</code> that reflects all those <code>#:</code> directives you were using.</p>\n<h2>Current Limitations</h2>\n<p>Right now, it's strictly single-file.\nIf you need <strong>multiple files</strong>, you'll have to <a href=\"https://github.com/dotnet/sdk/issues/48174\">wait for .NET 11</a> or convert to a full project.\nOf course, you can still reference other projects or packages, but the main script has to be in one file.</p>\n<p>The caching mechanism can occasionally get confused if you're rapidly iterating on package versions.\nAnd while the IDE support is getting better, it's not quite at the same level as full projects yet, especially for IntelliSense with dynamically referenced packages.</p>\n<p>But for what it's designed to do (make C# approachable for scripting scenarios), it works remarkably well.\nYou can use it for <strong>build scripts</strong>, <strong>data migration one-offs</strong>, quick API tests, or even teaching C# without overwhelming beginners with project structure.</p>\n<h2>Where This Leaves Us</h2>\n<p><strong>File-based apps</strong> feel like C# finally acknowledging that not every piece of code needs to be an enterprise application.\nSometimes you just need to parse a log file, sometimes you want to quickly test an algorithm,\nand sometimes you're teaching someone programming and don't want to explain what a solution file is on day one.</p>\n<p>The feature doesn't revolutionize C# development, but it <strong>fills a gap</strong> that's been annoying developers for years.\nIf you've been keeping a folder of scripts for quick tasks because C# felt too heavy, maybe it's time to give your favorite language another shot.</p>\n<p>After all, the best language for a quick script is the one you already know, and now C# makes that argument a lot more compelling.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/exploring-csharp-file-based-apps-in-dotnet-10",
            "title": "Exploring C# File-based Apps in .NET 10",
            "summary": "With .NET 10, Microsoft introduced file-based apps, which give you the ability to write C# code in a single .cs file and run it directly, without any project…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_168.png",
            "date_modified": "2025-11-15T00:00:00.000Z",
            "date_published": "2025-11-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it",
            "content_html": "<p>The Idempotent Consumer pattern records each processed message ID in a database table and checks it before handling a message.\nThe side effects and that record commit in the same transaction, so a redelivered duplicate short-circuits and produces no duplicate side effects.\nBrokers can deduplicate on the producer side, but only the consumer can defend against redeliveries.</p>\n<p>Distributed systems are unreliable by nature.</p>\n<p>I always recommend reading about the <a href=\"https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing\">Fallacies of Distributed Computing</a> to understand the common pitfalls.</p>\n<p>One of the key challenges is ensuring that messages are processed <strong>exactly once</strong>.\nTheoretically, that's impossible to guarantee in most systems.\nI won't dive into the <a href=\"https://en.wikipedia.org/wiki/CAP_theorem\">CAP theorem</a> or the\n<a href=\"https://en.wikipedia.org/wiki/Two_Generals%27_Problem\">Two Generals Problem</a> here, but suffice to say:</p>\n<ul>\n<li>Messages can arrive out of order</li>\n<li>Messages can be duplicated</li>\n<li>Deliveries can be delayed</li>\n</ul>\n<p>If you design your system assuming every message will be processed exactly once, you're setting yourself up for subtle data corruption.</p>\n<p>But we can design our system to apply side effects <strong>exactly once</strong> using the <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>Idempotent Consumer</strong></a> pattern.</p>\n<p>Let's unpack what can go wrong, how brokers help with idempotency, and how you can build an idempotent consumer in .NET.</p>\n<h2>What Can Go Wrong When Publishing</h2>\n<p>Let's say your service publishes an event when a new note is created:</p>\n<pre><code class=\"language-csharp\">await publisher.PublishAsync(new NoteCreated(note.Id, note.Title, note.Content));\n</code></pre>\n<p>We don't have to worry about the specific implementation of <code>publisher</code> or the message broker here.\nIt could be RabbitMQ, SQS, Azure Service Bus, etc.</p>\n<p>Now imagine:</p>\n<ul>\n<li>The publisher sends the message to the broker</li>\n<li>The broker stores it and sends an ACK</li>\n<li><strong>Network glitch</strong>: the ACK never reaches the producer</li>\n<li>Producer times out and <strong>retries</strong> the publish</li>\n<li>The broker now has two <code>NoteCreated</code> events</li>\n</ul>\n<p>From the producer's perspective, it &quot;fixed&quot; a timeout.\nBut from the consumer's perspective, it received two events for the same note creation.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_167/distributed_messaging_with_error.png\" alt=\"Distributed messaging with network error causing duplicate messages.\">\n<p>And that's just one failure path.\nYou can also get duplicates from:</p>\n<ul>\n<li>Broker redeliveries</li>\n<li>Consumer failures + retries</li>\n</ul>\n<p>So even if you do everything &quot;right&quot; on the publisher, the <strong>consumer still has to be defensive</strong>.</p>\n<h2>Publisher-Side Idempotency (Let the Broker Handle It)</h2>\n<p>Many message brokers already support idempotent publishing via message deduplication if you include a unique message ID.\n<a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\"><strong>Azure Service Bus</strong></a>, for instance, can detect duplicates and ignore re-publishes for the same message ID within a configured window.\n<a href=\"https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit\"><strong>Amazon SQS</strong></a> and other brokers also offer similar guarantees.</p>\n<p>You don't need to reinvent this logic in your application.\nThe key is to assign each message a stable identifier that uniquely represents the logical event you're sending.</p>\n<p>For example, when publishing a NoteCreated event:</p>\n<pre><code class=\"language-csharp\">var message = new NoteCreated(note.Id, note.Title, note.Content)\n{\n    MessageId = Guid.NewGuid() // or you can use note.Id\n};\n\nawait publisher.PublishAsync(message);\n\n</code></pre>\n<p>If the network drops after you send the message, your app might retry.\nBut when the broker sees the same <code>MessageId</code>, it knows this is a duplicate and safely discards it.\nYou get deduplication without any custom tracking tables or extra state in your service.</p>\n<p>This broker-level idempotency solves a large class of <strong>producer-side issues</strong>: network retries, transient failures, and duplicated publishes.</p>\n<p>What it doesn't handle are <strong>consumer retries</strong>, which happen when messages are redelivered or your service crashes mid-processing.</p>\n<p>That's where the idempotent consumer pattern comes in.</p>\n<h2>Implementing an Idempotent Consumer in .NET</h2>\n<p>Here's an example of an idempotent consumer for a <code>NoteCreated</code> event:.</p>\n<pre><code class=\"language-csharp\">internal sealed class NoteCreatedConsumer(\n    TagsDbContext dbContext,\n    HybridCache hybridCache,\n    ILogger&lt;Program&gt; logger) : IConsumer&lt;NoteCreated&gt;\n{\n    public async Task ConsumeAsync(ConsumeContext&lt;NoteCreated&gt; context)\n    {\n        // 1. Check if we've already processed this message for this consumer\n        if (await dbContext.MessageConsumers.AnyAsync(c =&gt;\n                c.MessageId == context.MessageId &amp;&amp;\n                c.ConsumerName == nameof(NoteCreatedConsumer)))\n        {\n            return;\n        }\n\n        var request = new AnalyzeNoteRequest(\n            context.Message.NoteId,\n            context.Message.Title,\n            context.Message.Content);\n\n        try\n        {\n            using var transaction = await dbContext.Database.BeginTransactionAsync();\n\n            // 2. Deterministic processing: derive tags from note content\n            var tags = AnalyzeContentForTags(request.Title, request.Content);\n\n            // 3. Persist tags\n            var tagEntities = tags.Select(ProjectToTagEntity(request.NoteId)).ToList();\n            dbContext.Tags.AddRange(tagEntities);\n\n            // 4. Record that this message was processed\n            dbContext.MessageConsumers.Add(new MessageConsumer\n            {\n                MessageId = context.MessageId,\n                ConsumerName = nameof(NoteCreatedConsumer),\n                ConsumedAtUtc = DateTime.UtcNow\n            });\n\n            await dbContext.SaveChangesAsync();\n            await transaction.CommitAsync();\n\n            // 5. Update cache\n            await CacheNoteTags(request, tags);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, &quot;Error analyzing note {NoteId}&quot;, request.NoteId);\n            throw;\n        }\n    }\n}\n</code></pre>\n<p>This is a typical idempotent consumer with a few important details.</p>\n<p><strong>1. The Idempotency Key</strong></p>\n<pre><code class=\"language-csharp\">if (await dbContext.MessageConsumers.AnyAsync(c =&gt;\n        c.MessageId == context.MessageId &amp;&amp;\n        c.ConsumerName == nameof(NoteCreatedConsumer)))\n{\n    return;\n}\n</code></pre>\n<p>You use:</p>\n<ul>\n<li><code>MessageId</code> from the transport (<code>context.MessageId</code>)</li>\n<li><code>ConsumerName</code> (so multiple consumers can safely process the same message)</li>\n</ul>\n<p>If a duplicate message arrives, you short-circuit and do nothing.</p>\n<p>What's also important here is having a <strong>unique constraint</strong> on <code>(MessageId, ConsumerName)</code> in the <code>MessageConsumers</code> table to prevent\n<a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\"><strong>race conditions</strong></a>.\nSo even if you have concurrent processing of the same message, only one will succeed in inserting the record.</p>\n<p><strong>2. Atomic Side Effects + Idempotency Record</strong></p>\n<p>The processing and storing the message consumer record happen <strong>in the same transaction</strong>:</p>\n<pre><code class=\"language-csharp\">using var transaction = await dbContext.Database.BeginTransactionAsync();\n\n// write tags\ndbContext.Tags.AddRange(tagEntities);\n\n// write message-consumer record\ndbContext.MessageConsumers.Add(new MessageConsumer { ... });\n\nawait dbContext.SaveChangesAsync();\nawait transaction.CommitAsync();\n</code></pre>\n<p>Why this matters:</p>\n<ul>\n<li>If processing fails, there is no entry in <code>MessageConsumers</code>, so the message can be retried</li>\n<li>If processing succeeds, both the tags and the <code>MessageConsumer</code> row are committed together</li>\n<li>You never end up in a state where the work is done but the message is not marked as processed, or vice versa</li>\n</ul>\n<p>This is the core of idempotency:</p>\n<blockquote>\n<p>Do this work exactly once per message ID, even under retries.</p>\n</blockquote>\n<p><strong>3. Handling At-Least-Once Delivery</strong></p>\n<p>Most realistic setups are <strong>at-least-once</strong>:</p>\n<ul>\n<li>Consumer processes message</li>\n<li>ACK fails / times out</li>\n<li>Broker redelivers</li>\n<li>Your code runs again</li>\n</ul>\n<p>With this pattern, the second run hits the <code>MessageConsumers</code> table and returns early.</p>\n<p>No duplicate side effects.</p>\n<p>This works, except for one caveat...</p>\n<h2>Deterministic vs Non-Deterministic Handlers</h2>\n<p>What happens when your handler calls something <em>outside</em> the database?\nAn email API, a payment gateway, or a background job queue?</p>\n<p>These are all common side effects that need to be idempotent too.</p>\n<p>Those calls sit outside your transaction boundary.\nYour database might commit successfully, but if the network hiccups before the external service responds, you can't tell if the action happened or not.\nOn retry, your consumer might send another email or charge the credit card twice.</p>\n<p>You've now crossed into the messy territory of non-deterministic handlers: operations that can't be repeated safely.</p>\n<p>There are two main strategies to deal with this.</p>\n<p><strong>1. Use an Idempotency Key in the External Call</strong></p>\n<p>If the external service supports it, pass a stable identifier, like the message's <code>MessageId</code> with every request.\nMany APIs, including payment processors and email platforms, let you specify an idempotency key header.\nThe service ensures that identical requests with the same key only execute once.</p>\n<p>For example:</p>\n<pre><code class=\"language-csharp\">await emailService.SendAsync(new SendEmailRequest\n{\n    To = user.Email,\n    Subject = &quot;Welcome!&quot;,\n    Body = &quot;Thanks for signing up.&quot;,\n    IdempotencyKey = context.MessageId\n});\n\n</code></pre>\n<p>Even if the request is retried, the provider will recognize the key and skip the duplicate.\nThis is the simplest and most reliable approach, if your external dependency supports it.</p>\n<p><strong>2. Store the Intent Locally</strong></p>\n<p>If the external service doesn't support idempotency keys, you can simulate it.\nStore a <strong>record of the intended action</strong> in your database before calling the external system.\nFor example, create a <code>PendingEmails</code> table that records which messages should be sent, keyed by message ID or user ID.</p>\n<p>A background process can later read these pending records and perform the action once.\nThis makes the process deterministic, but at the cost of more complexity, extra tables, and background workers.\nIt's often overengineering unless the side effect is critical or irreversible, like payments or account provisioning.</p>\n<p>The trade-off comes down to confidence.\nIf repeating the action has real consequences, introduce idempotency explicitly.\nIf not, retrying the operation might be acceptable.</p>\n<h2>When Idempotent Consumer Isn't Needed</h2>\n<p>Not every consumer needs the overhead of idempotency checks.\nIf your operation is already naturally idempotent, you can often skip the extra table and transaction logic.</p>\n<p>Updating a projection, setting a status flag, or refreshing a cache are all examples of deterministic actions that can safely run multiple times.\nFor instance, &quot;set user's status to Active&quot; or &quot;rebuild the read model&quot; are operations that overwrite state rather than append to it.</p>\n<p>Some handlers also use precondition checks to avoid duplication.\nIf the handler updates an entity, it can first check whether that entity is already in the desired state and return early.\nThat simple guard clause can be enough.</p>\n<p>Don't blindly apply the <strong>Idempotent Consumer</strong> pattern everywhere.\nApply it where it protects you from real harm, where duplicate processing causes financial or data inconsistencies.</p>\n<p>For everything else, <strong>simpler is better</strong>.</p>\n<h2>Takeaway</h2>\n<p>Distributed systems are unpredictable.\nRetries, duplicates, and partial failures are part of normal operation.\nYou can't avoid them, but you can design your system so they don't impact you as much.</p>\n<p>Use your broker's built-in <strong>message deduplication</strong> to prevent duplicates from the producer side.\nFor the consumer side, apply the <strong>Idempotent Consumer</strong> pattern to ensure side effects happen once, even under retries.\nKeep the record of processed messages and the actual side effect in the same transaction.</p>\n<p>Not every message handler needs this.\nIf your consumer is naturally idempotent or can short-circuit with a simple precondition, skip the extra complexity.\nBut for anything that modifies persistent state or calls external systems, idempotency isn't optional, it's the only way to keep your system consistent.</p>\n<p>Build your consumers to tolerate retries.\nAnd your distributed system will be that much more reliable.\nThe interesting part is that once you understand this principle, you start seeing it everywhere in real-world systems.</p>\n<p>If you want to dive deeper into messaging patterns and learn how this is implemented in a production-grade system,\ncheck out my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course.\nWe'll build a full-featured application with distributed messaging, CQRS, and DDD patterns from scratch.</p>\n<p>Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it",
            "title": "The Idempotent Consumer Pattern in .NET (And Why You Need It)",
            "summary": "Distributed systems don't fail cleanly: they retry, duplicate, and occasionally fail. Here's how to design resilient message handlers in .NET with broker-level…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_167.png",
            "date_modified": "2025-11-08T00:00:00.000Z",
            "date_published": "2025-11-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/whats-new-in-ef-core-10-leftjoin-and-rightjoin-operators-in-linq",
            "content_html": "<p>.NET 10 adds first-class <code>LeftJoin</code> and <code>RightJoin</code> methods to LINQ, and EF Core translates them to <code>LEFT JOIN</code> and <code>RIGHT JOIN</code> in SQL.\nBefore that, a left join meant combining <code>GroupJoin</code>, <code>DefaultIfEmpty</code>, and <code>SelectMany</code>.\nEF Core generates the same SQL either way, so the win is fewer moving parts and clearer intent.</p>\n<p>If you've ever worked with databases, you know about <code>LEFT JOIN</code> (and conversely <code>RIGHT JOIN</code>).\nIt's one of those things we use often, if not all the time.\nBut in Entity Framework Core, doing a left join has always been... well, a bit of a pain.</p>\n<p>I like joins that read like what they do.\nUnfortunately, until now, LINQ didn't have a straightforward way to express left/right joins.\nYou had to jump through hoops with <code>GroupJoin</code> and <code>DefaultIfEmpty</code>, which made the code harder to read and maintain.</p>\n<p>But <strong>.NET 10</strong> finally fixes this with the brand new <code>LeftJoin</code> and <code>RightJoin</code> methods.</p>\n<h2>What's a LEFT JOIN (in plain words)?</h2>\n<p>A <strong>LEFT JOIN</strong> returns <strong>all rows from the left side</strong> and the <strong>matching rows from the right side</strong>.\nIf there's <strong>no match</strong>, the right side is <strong>null</strong>.\nWhy we use it: to keep &quot;owners&quot; even when they have <strong>no related rows</strong> (e.g., show all products even if some don't have a review).</p>\n<figure className=\"figure-center bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_166/left_join.gif\" alt=\"Animated left join.\">\n  <figcaption>\n    Source: <a href=\"https://dataschool.com/how-to-teach-people-sql/left-right-join-animated/\">Data\nSchool</a>\n  </figcaption>\n</figure>\n<h2>The Old Way (<code>GroupJoin</code> + <code>DefaultIfEmpty</code>)</h2>\n<p>Before .NET 10, a left join in LINQ needed a <strong>group join</strong> (<code>GroupJoin</code>), then <code>DefaultIfEmpty</code> to keep left rows with no match.\nIt worked, but the intent was buried in noise.</p>\n<p>There are two ways you could write it: <strong>query syntax</strong> and <strong>method syntax</strong>.</p>\n<h3>Query syntax</h3>\n<pre><code class=\"language-csharp\">var query =\n    from product in dbContext.Products\n    join review in dbContext.Reviews on product.Id equals review.ProductId into reviewGroup\n    from subReview in reviewGroup.DefaultIfEmpty()\n    orderby product.Id, subReview.Id\n    select new\n    {\n        ProductId = product.Id,\n        product.Name,\n        product.Price,\n        ReviewId = (int?)subReview.Id ?? 0,\n        Rating = (int?)subReview.Rating ?? 0,\n        Comment = subReview.Comment ?? &quot;N/A&quot;\n    };\n</code></pre>\n<p>Here's the SQL generated by EF Core for the above query:</p>\n<pre><code class=\"language-sql\">SELECT\n    p.&quot;Id&quot; AS &quot;ProductId&quot;,\n    p.&quot;Name&quot;,\n    p.&quot;Price&quot;,\n    COALESCE(r.&quot;Id&quot;, 0) AS &quot;ReviewId&quot;,\n    COALESCE(r.&quot;Rating&quot;, 0) AS &quot;Rating&quot;,\n    COALESCE(r.&quot;Comment&quot;, 'N/A') AS &quot;Comment&quot;\nFROM &quot;Products&quot; AS p\nLEFT JOIN &quot;Reviews&quot; AS r ON p.&quot;Id&quot; = r.&quot;ProductId&quot;\nORDER BY p.&quot;Id&quot;, COALESCE(r.&quot;Id&quot;, 0)\n</code></pre>\n<h3>Method syntax</h3>\n<pre><code class=\"language-csharp\">var query = dbContext.Products\n    .GroupJoin(\n        dbContext.Reviews,\n        product =&gt; product.Id,\n        review =&gt; review.ProductId,\n        (product, reviewList) =&gt; new { product, subgroup = reviewList })\n    .SelectMany(\n        joinedSet =&gt; joinedSet.subgroup.DefaultIfEmpty(),\n        (joinedSet, review) =&gt; new\n        {\n            ProductId = joinedSet.product.Id,\n            joinedSet.product.Name,\n            joinedSet.product.Price,\n            ReviewId = (int?)review!.Id ?? 0,\n            Rating = (int?)review!.Rating ?? 0,\n            Comment = review!.Comment ?? &quot;N/A&quot;\n        })\n    .OrderBy(result =&gt; result.ProductId)\n    .ThenBy(result =&gt; result.ReviewId);\n</code></pre>\n<p>Why this works: <code>GroupJoin</code> matches rows, <code>DefaultIfEmpty</code> inserts a single <strong>default</strong> (null) when no matches exist, so the left row still appears.\nWe then <strong>flatten</strong> with <code>SelectMany</code>.</p>\n<p>I think we can all agree that this is way too verbose for something as common as a left join.</p>\n<h2>The New Way in EF 10: <code>LeftJoin</code></h2>\n<p>Now we can write what we mean.\n<code>LeftJoin</code> is <strong>first-class LINQ</strong> and EF Core translates it to a <strong>LEFT JOIN</strong> in SQL.</p>\n<pre><code class=\"language-csharp\">var query = dbContext.Products\n    .LeftJoin(\n        dbContext.Reviews,\n        product =&gt; product.Id,\n        review =&gt; review.ProductId,\n        (product, review) =&gt; new\n        {\n            ProductId = product.Id,\n            product.Name,\n            product.Price,\n            ReviewId = (int?)review.Id ?? 0,\n            Rating = (int?)review.Rating ?? 0,\n            Comment = review.Comment ?? &quot;N/A&quot;\n        })\n    .OrderBy(x =&gt; x.ProductId)\n    .ThenBy(x =&gt; x.ReviewId)\n</code></pre>\n<p>The generated SQL is identical to the previous example.</p>\n<p>Why this is better:</p>\n<ul>\n<li><strong>Intent is clear</strong>: you see <code>LeftJoin</code>, you know what to expect.</li>\n<li><strong>Less code</strong>, fewer moving parts (no <code>GroupJoin</code>, no <code>DefaultIfEmpty</code>, no <code>SelectMany</code>).</li>\n<li><strong>Same result</strong>: all products kept, reviews may be null.</li>\n</ul>\n<div className=\"note-panel\">\n  **Note**: At the time of writing this article, C# **query syntax** (`from …\n  select …`) doesn't have a `left join` or `right join` keyword yet. You should\n  use the method syntax shown above.\n</div>\n<h2>Also New: <code>RightJoin</code></h2>\n<p><code>RightJoin</code> keeps <strong>all rows from the right side</strong> and only matching rows from the left.\nEF Core translates it to <strong>RIGHT JOIN</strong>.\nIt's handy when the &quot;must keep&quot; side is the <strong>second</strong> sequence.</p>\n<p>Conceptually:</p>\n<pre><code class=\"language-csharp\">var query = dbContext.Reviews\n    .RightJoin(\n        dbContext.Products,\n        review =&gt; review.ProductId,\n        product =&gt; product.Id,\n        (review, product) =&gt; new\n        {\n            ProductId = product.Id,\n            product.Name,\n            product.Price,\n            ReviewId = (int?)review.Id ?? 0,\n            Rating = (int?)review.Rating ?? 0,\n            Comment = review.Comment ?? &quot;N/A&quot;\n        });\n</code></pre>\n<p>Why use <code>RightJoin</code>: when your reporting starts from <strong>Reviews</strong> (keep all), and bring in matching <strong>Products</strong> if they exist.</p>\n<p>Here's the generated SQL:</p>\n<pre><code class=\"language-sql\">SELECT\n    p.&quot;Id&quot; AS &quot;ProductId&quot;,\n    p.&quot;Name&quot;,\n    p.&quot;Price&quot;,\n    COALESCE(r.&quot;Id&quot;, 0) AS &quot;ReviewId&quot;,\n    COALESCE(r.&quot;Rating&quot;, 0) AS &quot;Rating&quot;,\n    COALESCE(r.&quot;Comment&quot;, 'N/A') AS &quot;Comment&quot;\nFROM &quot;Reviews&quot; AS r\nRIGHT JOIN &quot;Products&quot; AS p ON r.&quot;ProductId&quot; = p.&quot;Id&quot;\n</code></pre>\n<h2>Wrapping Up</h2>\n<p>Think about how often you need left joins.\nShowing all users with optional profile settings.\nAll products with optional reviews.\nAll orders with optional shipping info.\nIt's everywhere!</p>\n<p>Before, developers would sometimes skip the proper left join and do two separate queries instead.\nOr worse, they'd use an inner join and miss data.\nNow there's no excuse - it's just as easy as any other LINQ method.</p>\n<p>A few quick tips when writing LINQ queries:</p>\n<ul>\n<li>In the projection, guard nullable side: <code>review.Comment ?? &quot;N/A&quot;</code></li>\n<li><a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>Keep projections small</strong></a> to avoid pulling more columns than needed</li>\n<li>Add indexes on the <strong>join keys</strong> for better query plans</li>\n</ul>\n<p>That's it.\nWith <code>LeftJoin</code> and <code>RightJoin</code>, the code finally matches the mental model.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/whats-new-in-ef-core-10-leftjoin-and-rightjoin-operators-in-linq",
            "title": "What's New in EF Core 10: LeftJoin and RightJoin Operators in LINQ",
            "summary": ".NET 10 finally adds proper LeftJoin and RightJoin methods to LINQ, replacing the complex GroupJoin + DefaultIfEmpty pattern with clean, readable code that…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_166.png",
            "date_modified": "2025-11-01T00:00:00.000Z",
            "date_published": "2025-11-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-interview-question-that-changed-how-i-think-about-system-design",
            "content_html": "<p>A report that takes five minutes to generate should not block the user on a long HTTP request.\nAccept the request, save it as a job record, and return immediately.\nA background worker builds the file, uploads it to S3 or Azure Blob, and notifies the user with a download link.</p>\n<p>About six or seven years ago, I went through an interview for a mid-level .NET role.</p>\n<p>One of the questions has stayed with me ever since:</p>\n<blockquote>\n<p>A user clicks a button on the UI to generate an Excel or PDF report.\nThe report generation takes around five minutes (time can be arbitrary).\nThe user has to wait for it to finish.\nHow would you optimize this flow?</p>\n</blockquote>\n<p>At the time, I focused on what I knew best: <strong>performance</strong>.\nI started thinking about how to make the report generation faster.\nMaybe I could optimize the SQL queries, reduce data transformations, or <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>cache</strong></a> parts of the result.\nIf I could get the process down from five minutes to one, that felt like a big win.</p>\n<p>But even if I made it five times faster, the user still had to wait.\nIf the browser crashed, they lost everything.\nIf the network dropped, the process stopped.\nIf they closed the tab, all progress was gone.</p>\n<p>It wasn't really a performance issue at all, it was a <strong>design issue</strong>.</p>\n<h2>What I Missed Back Then</h2>\n<p>Looking back, I realize I was <strong>stuck</strong> in the mindset of &quot;make the code faster&quot;.\nNot that there's anything wrong with that, <strong>performance optimization is a valuable skill</strong>.\nWhat I didn't see immediately was the <strong>bigger problem</strong>.\nThe app was doing all this work <strong>synchronously</strong>, holding the user hostage until it finished.\nI did eventually figure it out, with a few nudges from the interviewer.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_165/sync_reporting_flow.png\" alt=\"Synchronous report generation sequence where the user waits while the API builds and returns the file\">\n<p>The better question wasn't <em>&quot;How can I make this faster?&quot;</em></p>\n<p>It was <em>&quot;Why is the user waiting in the first place?&quot;</em></p>\n<p>If something takes minutes (or hours, days) to complete, it shouldn't block the user.\nIt should happen in the <strong>background</strong>, out of the <strong>main request flow</strong>, while the user moves on with their work.</p>\n<p>Still, don't forget to optimize the code itself.\nDatabase queries, data processing, and file generation all matter.\nMaybe there's a missing index, an inefficient loop, or a better library for creating Excel files.\nBut those optimizations are just part of the solution, not the whole picture.</p>\n<h2>How I'd Solve It Today</h2>\n<p>Today, I'd still start with the same UI button.\nThe user clicks &quot;Generate Report,&quot; but instead of waiting, the <strong>backend accepts the request</strong>,\nsaves it somewhere (maybe as a job record in a database), and <strong>returns right away</strong>.\nThis is the essence of building <a href=\"https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way\"><strong>asynchronous APIs</strong></a>.\nThe job is then picked up by a background worker.</p>\n<p>The worker can be a <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>hosted service</strong></a>,\na <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-in-dotnet-advanced-concepts\"><strong>Quartz job</strong></a>,\nor even an <a href=\"https://milanjovanovic.tech/blog/building-fast-serverless-apis-with-minimal-apis-on-aws-lambda\"><strong>AWS Lambda Function</strong></a> triggered by a queue message.\nIt handles the heavy lifting: pulling the data, building the file, and uploading it to storage like S3 or Azure Blob.</p>\n<p>Once the report is ready, the worker updates the job status to &quot;completed&quot; and notifies the user.\nThat could be an email with a download link or a real-time <a href=\"https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr\"><strong>SignalR</strong></a> message that shows up in the app.\nThe link points to the stored report, served securely from the backend.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_165/async_reporting_flow.png\" alt=\"Asynchronous report generation sequence using a job queue, background worker, object storage, and user notification\">\n<p>Now, the user isn't waiting on a long-running HTTP request.\nThe server isn't holding open connections for minutes.\n<strong>If something fails, it can be retried automatically</strong>.\nYou also have the option to <strong>track progress</strong> or <strong>cancel</strong> the job if needed.\nAnd if a hundred users request reports at once, the system can scale without locking up.</p>\n<p>The experience feels faster, even if the actual report generation time hasn't changed.\nBecause in the end, users don't care about performance metrics, they care about responsiveness.</p>\n<h2>Why I Still Use This Question</h2>\n<p>A few years later, I started using this exact question when interviewing other developers.\nNot to trick anyone, but because it reveals how people think.</p>\n<p>Some candidates go straight to optimizing code and queries, just like I did back then.\nThis is a good sign that they know their way around performance tuning.\nI can proceed with further technical questions around <strong>algorithms</strong>, <strong>data structures</strong>, or <strong>database optimization</strong>.</p>\n<p>Others pause for a moment and start thinking about user experience, <strong>background processing</strong>, and <strong>fault tolerance</strong>.\nThat's when the real conversation begins: queues, retries, notifications, secure file sharing, etc.\nThere are so many ways you can spin off this one scenario into a broader system design discussion.</p>\n<p>There's <strong>no single right answer</strong>.\nBut there's a big difference between someone who focuses only on <em>code</em> and someone who can design a <em>scalable system</em>.</p>\n<h2>The Lesson</h2>\n<p>When I first heard this question, I thought about making the code faster.\nNow I think about making the experience better.</p>\n<p>Optimizing a query or loop can help, but it doesn't fix waiting, failures, or scalability.\nIf many users start the same report at once, a synchronous design breaks down fast.\nAn asynchronous flow keeps the system responsive and resilient, no matter the load.</p>\n<p>That shift from optimizing functions to designing scalable systems is the difference between a good developer and a great one.</p>\n<p>If you want to go deeper into building systems that scale, my <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture course</strong></a> walks you through exactly that.\nYou'll learn how to structure applications, separate concerns, and design systems that grow without breaking.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-interview-question-that-changed-how-i-think-about-system-design",
            "title": "The Interview Question That Changed How I Think About System Design",
            "summary": "Discover how a simple interview question about report generation reveals the difference between optimizing code and designing scalable systems, and why the…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_165.png",
            "date_modified": "2025-10-25T00:00:00.000Z",
            "date_published": "2025-10-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/6-steps-for-setting-up-a-new-dotnet-project-the-right-way",
            "content_html": "<p>Set up a new .NET project in six steps: an <code>.editorconfig</code> for code style, <code>Directory.Build.props</code> for shared build settings, and <code>Directory.Packages.props</code> for central package management.\nThen add static analysis with SonarAnalyzer, Docker Compose or .NET Aspire for local orchestration, and a GitHub Actions build.\nThe whole setup happens before you write any business logic.</p>\n<p>Starting a new .NET project is always exciting.\nBut it's also easy to skip the groundwork that makes a project scalable and maintainable.</p>\n<p>Before you write your first line of business logic, there are a few key setup steps that make your life (and your teammates) much easier later on.</p>\n<p>Here's how I usually set up a new .NET project in <strong>six simple steps</strong>.</p>\n<h2>1. Enforce a Consistent Code Style</h2>\n<p>The first thing I add is an <code>.editorconfig</code> file.</p>\n<p>This file ensures everyone on your team uses the same formatting and naming conventions, reducing inconsistent indents or random naming rules.</p>\n<p>You can create one directly in Visual Studio:</p>\n<div class=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_164/add_editorconfig.png\" alt=\"Visual Studio Add menu with New EditorConfig selected\">\n</div>\n<p>The default configuration is a great start.\nBut you can customize it further to fit your team's preferences.</p>\n<p>Place it at the <strong>solution root</strong> so all projects follow the same rules.\nYou can still override specific settings in subfolders if needed by placing an <code>.editorconfig</code> file there.</p>\n<p>Here are two sample <code>.editorconfig</code> files you can use:</p>\n<ul>\n<li><a href=\"https://github.com/dotnet/runtime/blob/main/.editorconfig\">From the .NET runtime repo</a></li>\n<li><a href=\"https://gist.github.com/m-jovanovic/417b7d0a641d7dd7d1972550fba298db\">Created by me for general .NET projects</a></li>\n</ul>\n<h2>2. Centralize Build Configuration</h2>\n<p>Next, I add a <code>Directory.Build.props</code> file to the solution root.\nThis file lets you define build settings that apply to every project in the solution.</p>\n<p>Here's an example:</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;\n    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;\n    &lt;Nullable&gt;enable&lt;/Nullable&gt;\n    &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;\n  &lt;/PropertyGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>This keeps your <code>.csproj</code> files clean and consistent, since there's no need to repeat these properties in every project.</p>\n<p>If you later want to enable static analyzers or tweak build options, you can do it once here.</p>\n<p>What's cool about this is your <code>.csproj</code> files become basically empty, with only NuGet package references most of the time.</p>\n<h2>3. Centralize Package Management</h2>\n<p>As your solution grows, managing NuGet package versions across multiple projects gets painful.</p>\n<p>That's where <a href=\"https://milanjovanovic.tech/blog/central-package-management-in-net-simplify-nuget-dependencies\"><strong>central package management</strong></a> helps.</p>\n<p>Create a file named <code>Directory.Packages.props</code> at the root:</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n  &lt;PropertyGroup&gt;\n    &lt;ManagePackageVersionsCentrally&gt;true&lt;/ManagePackageVersionsCentrally&gt;\n  &lt;/PropertyGroup&gt;\n\n  &lt;ItemGroup&gt;\n    &lt;PackageVersion Include=&quot;Microsoft.AspNetCore.OpenApi&quot; Version=&quot;10.0.0&quot; /&gt;\n    &lt;PackageVersion Include=&quot;SonarAnalyzer.CSharp&quot; Version=&quot;10.15.0.120848&quot; /&gt;\n  &lt;/ItemGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>Now, when you reference a NuGet package in your project, you don't specify the version.\nYou can only use the package name like this:</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;Microsoft.AspNetCore.OpenApi&quot; /&gt;\n</code></pre>\n<p>All versioning is handled centrally.\nThis makes dependency upgrades trivial and avoids version drift between projects.</p>\n<p>You can still override versions in individual projects if needed.</p>\n<h2>4. Add Static Code Analysis</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/improving-code-quality-in-csharp-with-static-code-analysis\"><strong>Static code analysis</strong></a> helps catch potential bugs and maintain code quality.\n.NET has a set of built-in analyzers, but I like to add <strong>SonarAnalyzer.CSharp</strong> for more comprehensive checks.</p>\n<p>Let's install <strong>SonarAnalyzer.CSharp</strong> to catch potential code issues early:</p>\n<pre><code class=\"language-powershell\">Install-Package SonarAnalyzer.CSharp\n</code></pre>\n<p>Add it as a global package reference inside your <code>Directory.Build.props</code>:</p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;PackageReference Include=&quot;SonarAnalyzer.CSharp&quot; /&gt;\n&lt;/ItemGroup&gt;\n</code></pre>\n<p>Combine this with:</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n  &lt;PropertyGroup&gt;\n    &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;\n    &lt;AnalysisLevel&gt;latest&lt;/AnalysisLevel&gt;\n    &lt;AnalysisMode&gt;All&lt;/AnalysisMode&gt;\n    &lt;CodeAnalysisTreatWarningsAsErrors&gt;true&lt;/CodeAnalysisTreatWarningsAsErrors&gt;\n    &lt;EnforceCodeStyleInBuild&gt;true&lt;/EnforceCodeStyleInBuild&gt;\n  &lt;/PropertyGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>…and your build will fail on serious code quality issues.\nThis can be a great safety net.</p>\n<p>But it can also be noisy at first.\nIf some rules don't fit your context, you can adjust or suppress them in <code>.editorconfig</code> by setting the rule severity to <code>none</code>.</p>\n<h2>5. Set Up Local Orchestration</h2>\n<p>For a consistent local environment across your team, you'll want container orchestration.</p>\n<p>You have two main options:</p>\n<p><strong>Option 1: Docker Compose</strong></p>\n<p>Add <strong>Docker Compose support</strong> in Visual Studio.\nIt will scaffold a <code>docker-compose.yml</code> file where you can define services like:</p>\n<pre><code class=\"language-yaml\">services:\n  webapi:\n    build: .\n  postgres:\n    image: postgres:18\n    environment:\n      POSTGRES_PASSWORD: password\n</code></pre>\n<p>This lets every developer spin up the same stack locally with one command.</p>\n<p><strong>Option 2: .NET Aspire</strong></p>\n<p><a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a> takes orchestration a step further.\nIt provides <a href=\"https://milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery\"><strong>service discovery</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>telemetry</strong></a>, and streamlined configuration, all integrated with your .NET projects.\nIt's become a <strong>personal favorite of mine</strong>.</p>\n<p>You can add a .NET project and a Postgres resource with a few lines of code:</p>\n<pre><code class=\"language-csharp\">var postgres = builder.AddPostgres(&quot;demo-db&quot;);\n\nbuilder.AddProject&lt;WebApi&gt;(&quot;webapi&quot;)\n       .WithReference(postgres)\n       .WaitFor(postgres);\n\nbuilder.Build().Run();\n</code></pre>\n<p>Aspire still uses <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker</strong></a> under the hood but provides a richer developer experience.</p>\n<p>Whether you pick Docker Compose or Aspire, the goal is the same: a repeatable, reliable local setup that works the same on every machine.</p>\n<h2>6. Automate Builds with CI</h2>\n<p>Finally, I set up a simple <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>GitHub Actions</strong></a> workflow to validate each commit.</p>\n<p>Example <code>.github/workflows/build.yml</code>:</p>\n<pre><code class=\"language-yaml\">name: Build\n\non:\n  push:\n    # Filter to only run on main branch\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-dotnet@v5\n        with:\n          dotnet-version: 10.0.x\n      - run: dotnet restore\n      - run: dotnet build --no-restore --configuration Release\n      - run: dotnet test --no-build --configuration Release\n</code></pre>\n<p>This ensures your project always builds and passes tests, and it catches issues before they reach production.\nIf the CI build fails, you know something's wrong right away.</p>\n<p>When it comes to testing, I highly recommend exploring:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/shift-left-with-architecture-testing-in-dotnet\"><strong>Architecture testing</strong></a> to enforce architectural rules in your codebase</li>\n<li><a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>Integration testing with Testcontainers</strong></a>\nto spin up real dependencies in Docker during tests (you can run this locally and in CI)</li>\n</ul>\n<p>This will give you confidence that your code works as expected in an (as close as possible) production-like environment.</p>\n<h2>Wrapping Up</h2>\n<p>That's a wrap.\nYour <strong>new .NET project</strong> is now set up with:</p>\n<ul>\n<li>consistent code style</li>\n<li>centralized build and package management</li>\n<li>code quality enforcement</li>\n<li>reproducible local orchestration</li>\n<li>continuous integration</li>\n</ul>\n<p>These small setup steps save countless hours down the road and keep your codebase clean, predictable, and ready to scale.</p>\n<p>Once your project setup is solid, the next step is designing scalable boundaries.\nIn my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course, I show how to grow a .NET application without turning it into a tangled mess,\nthrough clear module boundaries, messaging, and domain isolation.</p>\n<p>If you're looking for a practical walkthrough of these steps, check out <a href=\"https://youtu.be/QRgtcbxJlo0\"><strong>this video</strong></a>.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/6-steps-for-setting-up-a-new-dotnet-project-the-right-way",
            "title": "6 Steps for Setting Up a New .NET Project the Right Way",
            "summary": "Learn how to properly set up a new .NET project with EditorConfig for code consistency, Directory.Build.props for centralized configuration, central package…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_164.png",
            "date_modified": "2025-10-18T00:00:00.000Z",
            "date_published": "2025-10-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-fast-serverless-apis-with-minimal-apis-on-aws-lambda",
            "content_html": "<p>You can run ASP.NET Core Minimal APIs on AWS Lambda by installing <code>Amazon.Lambda.AspNetCoreServer.Hosting</code> and adding one line to <code>Program.cs</code>.\nThe same code still runs on Kestrel locally.\nIn my tests a cold start took just over 2 seconds, while warm requests came back in about 150 ms.</p>\n<p>Have you ever wondered if you could host a tiny .NET API without running servers 24/7?\nYou can!</p>\n<p>AWS Lambda lets you run code on-demand, and with the <a href=\"https://www.nuget.org/packages/Amazon.Lambda.AspNetCoreServer.Hosting\">Amazon.Lambda.AspNetCoreServer.Hosting</a> library,\nyou can plug an ASP.NET Core <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis\">Minimal APIs</a> straight into <a href=\"https://aws.amazon.com/lambda/\">AWS Lambda</a>.</p>\n<p>In this article we'll set up a minimal API, deploy it, and discuss how it performs.</p>\n<p>Don't worry if you're new to serverless, I'll keep everything simple enough to follow along.</p>\n<h2>Getting Your Minimal APIs Lambda-Ready</h2>\n<p>Let's start with the basics. You need just three things to turn your <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> into a Lambda function.</p>\n<p>First, create your API:</p>\n<pre><code class=\"language-bash\">dotnet new webapi -n MyLambdaApi\ncd MyLambdaApi\n</code></pre>\n<p>Second, add Amazon's hosting package:</p>\n<pre><code class=\"language-powershell\">Install-Package Amazon.Lambda.AspNetCoreServer.Hosting\n</code></pre>\n<p>Third, add one line to your <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\n// This line does all the Lambda magic\nbuilder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);\n\nvar app = builder.Build();\n\napp.MapGet(&quot;/&quot;, () =&gt; &quot;Hello from Lambda!&quot;);\n\napp.Run();\n</code></pre>\n<p>That's it.\nYour API now runs both locally (for testing) and in Lambda (for production).\nWhen you run locally, it uses <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel\">Kestrel</a> like normal.\nWhen you deploy the app to AWS, Lambda takes over.</p>\n<h2>Ship It to AWS</h2>\n<p>You'll need the <a href=\"https://github.com/aws/aws-extensions-for-dotnet-cli\">Lambda tools</a> installed:</p>\n<pre><code class=\"language-bash\">dotnet tool install -g Amazon.Lambda.Tools\n</code></pre>\n<p>Then deploy with one command:</p>\n<pre><code class=\"language-bash\">dotnet lambda deploy-function\n</code></pre>\n<p>The tool asks you a few questions (like the function name, which IAM role to use).\nPick the defaults if you're just testing.\nIn a minute or two, your API is live with a URL like: <code>https://[abc123xyz].lambda-url.[region-name].on.aws/</code>.</p>\n<p>From the AWS Management Console, you can find your function with its URL and all the other details.</p>\n<div class=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_163/aws_console_lambda_function.png\" alt=\"AWS Lambda console showing the test-lambda function overview and function URL\">\n</div>\n<h2>Measuring Cold Starts</h2>\n<p>Here's where things become interesting (and problematic).\nLambda functions &quot;go to sleep&quot; when nobody uses them.\nWaking them up takes time, this is the famous <strong>&quot;cold start&quot; problem</strong>.</p>\n<p>I ran some simple tests with a basic Minimal API:</p>\n<ul>\n<li>First request (cold): 2,153 ms</li>\n<li>Second request (warm): 154 ms</li>\n<li>Third request (warm): 143 ms</li>\n<li>After 10 minutes idle (cold again): 2,074 ms</li>\n</ul>\n<p>As you can see, the first request is slow due to the cold start.\nSubsequent requests are much faster, around 150 ms.\nAfter 10 minutes of inactivity, the function goes cold again, and the next request takes over 2 seconds.\nThere's an optimization feature called <a href=\"https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html\">SnapStart</a> that can help reduce cold start times,\nbut it has its own tradeoffs and isn't always suitable for every application.</p>\n<p>Another thing to note is that I'm in Europe, and my Lambda function is in the US East (N. Virginia) region.\nThis adds network latency, so your results may vary based on your location and the region you choose.</p>\n<p>Even when warm, the latency is higher than a typical server-hosted API.\nThis may be acceptable for low-traffic or non-critical endpoints, but it's something to consider.</p>\n<p>Let's move beyond a &quot;Hello World&quot; example to something more realistic.</p>\n<h2>CRUD Operations Benchmark</h2>\n<p>To see how a simple API performs in the real world, I built a tiny CRUD app that creates a record, fetches it, updates it and then deletes it.\nFirst I ran each operation once to see the raw latency.\nThen I used a load-testing tool with 100 virtual users (VUs) to measure average latency under a bit more stress.</p>\n<pre><code class=\"language-csharp\">// POST /products - Create new product\napp.MapPost(&quot;/products&quot;, async (CreateProductRequest request, NpgsqlDataSource dataSource) =&gt;\n{\n    const string sql =\n        &quot;&quot;&quot;\n        INSERT INTO Products (Name, Description, Price, CreatedAt)\n        VALUES (@Name, @Description, @Price, @CreatedAt)\n        RETURNING Id, Name, Description, Price, CreatedAt\n        &quot;&quot;&quot;;\n\n    await using var connection = await dataSource.OpenConnectionAsync();\n    var product = await connection.QueryFirstAsync&lt;Product&gt;(sql, new\n    {\n        request.Name,\n        request.Description,\n        request.Price,\n        CreatedAt = DateTime.UtcNow\n    });\n\n    return Results.Created(\n        $&quot;/products/{product.Id}&quot;,\n        new ProductResponse(\n            product.Id,\n            product.Name,\n            product.Description,\n            product.Price,\n            product.CreatedAt));\n});\n// Other endpoints omitted for brevity:\n// - GET /products/{id} - Get product by ID\n// - PUT /products/{id} - Update product\n// - DELETE /products/{id} - Delete product\n</code></pre>\n<p>The application uses .NET 8, <a href=\"https://www.npgsql.org/\">Npgsql</a> and <a href=\"https://www.learndapper.com/\">Dapper</a>\nto interact with a PostgreSQL database running on <a href=\"https://aws.amazon.com/rds/\">Amazon RDS</a>.\nYou can find the source code for this example (and the previous one) in <a href=\"https://github.com/m-jovanovic/minimal-apis-on-lambda\">this repository</a>.</p>\n<p>Here are the results:</p>\n<p><strong>Single-call latency</strong></p>\n<pre><code class=\"language-text\">| Operation  | Latency (ms) | Notes                           |\n| ---------- | ------------ | ------------------------------- |\n| Create     |     537      | Cold start plus object creation |\n| Read       |     134      | Simple GET of the new record    |\n| Update     |     140      | Changing one property           |\n| Delete     |     167      | Removing the record             |\n</code></pre>\n<p>The create call took half a second because it included a cold start and initialization overhead.\nOnce the function was warmed up, the other operations completed in under two hundred milliseconds.</p>\n<p><strong>Load test with 100 virtual users</strong></p>\n<p>During the load test, I simulated 100 clients hitting the API at once.\nAWS automatically scaled the Lambda function to handle the traffic, and average latencies dropped because the functions were already warm.\nHere are the averages:</p>\n<ul>\n<li><strong>CREATE avg</strong>: 129 ms</li>\n<li><strong>READ avg</strong>: 132 ms</li>\n<li><strong>UPDATE avg</strong>: 152 ms</li>\n<li><strong>DELETE avg</strong>: 144 ms</li>\n</ul>\n<p>These numbers show that once your function is up and running, Lambda can respond quite quickly even when many users are making requests.\nOf course, actual times will vary depending on what your API does and how it stores data.</p>\n<p>Remember that each of these operations involves network calls to the database, which adds latency.\nOverall, I don't find these numbers bad for a serverless setup.</p>\n<h2>Summary</h2>\n<p>Hosting minimal APIs in AWS Lambda is <strong>surprisingly straightforward</strong>.\nWith one library and a single method call, your ASP.NET Core code can run &quot;without servers&quot;.\nYou pay only for the compute you use.\nAlso, the AWS Lambda free tier includes one million free requests per month, which is great for testing and light usage.</p>\n<p>However, there are tradeoffs.\nBecause of cold starts and the overhead of starting the .NET runtime, latency isn't as low as hosting on a dedicated server.\nIf your API is mission-critical or requires sub-100 ms responses at all times, you may need to look at provisioned concurrency, containers, or a traditional host.</p>\n<p>Lambda shines for small, <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>event-driven</strong></a> or intermittent workloads that don't justify a full-time server.\nBut it's less suitable for latency-sensitive or heavy, long-running applications.\nFor occasional or low-traffic API endpoints, though, <strong>Lambda</strong> offers a <strong>cost-effective</strong> and <strong>simple</strong> option.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-fast-serverless-apis-with-minimal-apis-on-aws-lambda",
            "title": "Building Fast Serverless APIs With Minimal APIs on AWS Lambda",
            "summary": "Learn how to deploy ASP.NET Core Minimal APIs to AWS Lambda with just one library and a single line of code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_163.png",
            "date_modified": "2025-10-11T00:00:00.000Z",
            "date_published": "2025-10-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql",
            "content_html": "<p>EF Core calls PostgreSQL functions and stored procedures without dropping down to ADO.NET.\nUse <code>dbContext.Database.SqlQuery&lt;T&gt;</code> with an interpolated string for functions, aliasing columns to match your DTO, and <code>ExecuteSqlAsync</code> with <code>CALL</code> for procedures.\nThe interpolation is parameterized, so it is not a SQL injection risk.</p>\n<p>You're building a .NET application with <a href=\"https://learn.microsoft.com/en-us/ef/core/\">EF Core</a>.\nMost of your queries work fine with <a href=\"https://milanjovanovic.tech/blog/why-i-write-tall-linq-queries\"><strong>LINQ</strong></a>, but now you're hitting scenarios where you need something more.</p>\n<p>Maybe you have a complex report that joins five tables with aggregations and window functions.\nYour LINQ query generates SQL that's slower than you'd like, and you know you could write better SQL by hand.</p>\n<p>Or maybe you need to update inventory with proper locking to prevent race conditions.\nYou could manage <a href=\"https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core\"><strong>transactions</strong></a> and explicit locks in C#, but it feels like you're fighting the framework.</p>\n<p>Here's what usually happens: you search for &quot;EF Core stored procedures&quot; and find conflicting advice.\nSome articles say avoid raw SQL at all costs.\nOthers suggest abandoning EF entirely and writing ADO.NET.\nNeither feels right.</p>\n<p>Actually, <strong>EF Core works great with database functions and procedures</strong>.\nYou get the database's power for what it does best, and EF's convenience for everything else.\nLet me show you how this actually works.</p>\n<p>I'll use <a href=\"https://www.postgresql.org/\">PostgreSQL</a> for examples, but the same principles apply to SQL Server and other relational databases.</p>\n<h2>When Should You Even Use Raw SQL?</h2>\n<p>Let's be honest: most of the time, <strong>LINQ is fine</strong>.\nEF Core translates your C# into decent SQL, and you get type safety and refactoring support.</p>\n<p>But there are times when raw SQL makes more sense:</p>\n<p><strong>You need performance you can't get from LINQ</strong>.\nComplex aggregations with multiple joins, <a href=\"https://www.postgresql.org/docs/current/tutorial-window.html\">window functions</a>, or reporting queries often run faster when written directly in SQL.\nYou can test and tune the query in your database tool before bringing it into your code.</p>\n<p><strong>You're using database-specific features</strong>.\nPostgreSQL has powerful capabilities like <a href=\"https://www.postgresql.org/docs/current/textsearch.html\">full-text search</a>, JSON operators,\nand <a href=\"https://www.postgresql.org/docs/current/queries-with.html\">common table expressions (CTEs)</a> that don't always have clean LINQ equivalents.\nSometimes the straightest path is just writing the SQL.</p>\n<p><strong>You have existing database logic</strong>.\nIf your database already has stored procedures and functions (maybe from a <a href=\"https://milanjovanovic.tech/blog/what-rewriting-a-40-year-old-project-taught-me-about-software-development\"><strong>legacy system</strong></a>),\ncalling them directly beats rewriting everything in C#.</p>\n<p><strong>You need atomic operations with proper locking</strong>.\nA stored procedure that coordinates multiple updates with <code>FOR UPDATE</code> locks (<a href=\"https://milanjovanovic.tech/blog/scaling-the-outbox-pattern\"><strong>here's a good use case</strong></a>)\nis simpler and safer than trying to manage that from application code.</p>\n<p><strong>You want to reduce round trips</strong>.\nOne function call that aggregates data from five tables is more efficient than five separate LINQ queries.</p>\n<p>Now let's see how to actually do this.</p>\n<h2>Example 1: Simple Scalar Function</h2>\n<p>Here's a straightforward function that tells you how many tickets are left:</p>\n<pre><code class=\"language-sql\">CREATE OR REPLACE FUNCTION ticketing.tickets_left(p_ticket_type_id uuid)\nRETURNS numeric\nLANGUAGE sql\nAS $$\n  SELECT tt.available_quantity\n  FROM ticketing.ticket_types tt\n  WHERE tt.id = p_ticket_type_id\n$$;\n</code></pre>\n<p>Nothing fancy, just a query wrapped in a function.</p>\n<p>Calling it from EF Core is straightforward:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;ticket-types/{ticketTypeId}/available-quantity&quot;,\nasync (Guid ticketTypeId, EventManagementContext dbContext) =&gt;\n{\n    var result = await dbContext.Database.SqlQuery&lt;int&gt;(\n            $&quot;&quot;&quot;\n             SELECT ticketing.tickets_left({ticketTypeId}) AS &quot;Value&quot;\n             &quot;&quot;&quot;)\n        .FirstAsync();\n\n    return Results.Ok(result);\n});\n</code></pre>\n<p>Notice the <code>AS &quot;Value&quot;</code> alias.\nWhen EF Core maps to a primitive type, it expects a property named <code>Value</code>.\nThe quotes preserve the exact casing (PostgreSQL lowercases unquoted identifiers by default).</p>\n<p>The interpolated string syntax (<code>$&quot;{ticketTypeId}&quot;</code>) might look dangerous, but EF Core converts this into a parameterized query automatically.\nYou're not building SQL strings, you're using C# interpolation as a convenient syntax for parameters.</p>\n<h2>Example 2: Table-Valued Function</h2>\n<p>Functions can return entire result sets, which is where they really shine:</p>\n<pre><code class=\"language-sql\">CREATE OR REPLACE FUNCTION ticketing.customer_order_summary(p_customer_id uuid)\nRETURNS TABLE (\n    order_id uuid,\n    created_at_utc timestamptz,\n    total_price numeric,\n    currency text,\n    item_count numeric\n)\nLANGUAGE sql\nAS $$\nSELECT\n    o.id,\n    o.created_at_utc,\n    o.total_price,\n    o.currency,\n    COALESCE(SUM(oi.quantity), 0) AS item_count\nFROM ticketing.orders o\nLEFT JOIN ticketing.order_items oi ON oi.order_id = o.id\nWHERE o.customer_id = p_customer_id\nGROUP BY o.id, o.created_at_utc, o.total_price, o.currency\nORDER BY o.created_at_utc DESC\n$$;\n</code></pre>\n<p>This function joins orders with their items, aggregates quantities, and returns multiple rows.\nYou could write this in LINQ, but the SQL is clearer and you can test it directly in your database tool.</p>\n<p>To use it from C#, create a DTO that matches the function's output:</p>\n<pre><code class=\"language-csharp\">public class OrderSummaryDto\n{\n    public Guid OrderId { get; set; }\n    public DateTime CreatedAtUtc { get; set; }\n    public decimal TotalPrice { get; set; }\n    public string Currency { get; set; }\n    public int ItemCount { get; set; }\n}\n</code></pre>\n<p>Then query the function like any other table:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;customers/{customerId}/order-summary&quot;,\nasync (Guid customerId, EventManagementContext dbContext) =&gt;\n{\n    var orders = await dbContext.Database\n        .SqlQuery&lt;OrderSummaryDto&gt;(\n            $&quot;&quot;&quot;\n             SELECT\n                order_id AS OrderId,\n                created_at_utc AS CreatedAtUtc,\n                total_price AS TotalPrice,\n                currency AS Currency,\n                item_count AS ItemCount\n             FROM ticketing.customer_order_summary({customerId})\n             &quot;&quot;&quot;)\n        .ToListAsync();\n\n    return Results.Ok(orders);\n});\n</code></pre>\n<p>The key is mapping column names to your DTO properties using aliases.\nEF Core handles the rest automatically.</p>\n<p>This is a simple case without joins, but you can use this pattern in more complex queries too.\nHowever, you will have to project into DTOs manually since EF Core can't translate joins in raw SQL into entity graphs.\nUsually, you'll return a flat structure from functions anyway, and then map to richer models in C# if needed.</p>\n<h2>Understanding PostgreSQL Functions vs Procedures</h2>\n<p>PostgreSQL distinguishes between functions and procedures in important ways:</p>\n<p><strong>Functions</strong> are designed to <strong>return values</strong>.\nThey can return scalar values, tables, or even complex JSON objects.\nYou call them with <code>SELECT</code> and can use them in queries like any other expression.\nFunctions run within a transaction and can be used in <code>WHERE</code> clauses, joins, and other query contexts.</p>\n<p><strong>Procedures</strong> are designed for <strong>side effects</strong>.\nThey don't return values directly but can modify data and have <code>OUT</code> parameters.\nYou call them with <code>CALL</code> and they're ideal for complex operations that need to manage transactions explicitly or perform multiple related updates.</p>\n<p>Think of it this way: use functions when you need data, use procedures when you need to change something.</p>\n<p>This distinction matters because it affects how you design your database logic and how you call these routines from C#.</p>\n<p>Let's see an example of a procedure.</p>\n<h2>Example 3: Stored Procedure with Validation</h2>\n<p>Here's where procedures really prove their worth.\nLet's say you need to adjust ticket inventory, but you want to prevent <a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\"><strong>race conditions</strong></a> and validate the operation:</p>\n<pre><code class=\"language-sql\">CREATE OR REPLACE PROCEDURE ticketing.adjust_available_quantity(\n    p_ticket_type_id uuid,\n    p_delta numeric,\n    p_reason text DEFAULT 'manual-adjust'\n)\nLANGUAGE plpgsql\nAS $$\nDECLARE\n    v_qty numeric;\n    v_avail numeric;\n    v_new_avail numeric;\nBEGIN\n    SELECT quantity, available_quantity\n    INTO v_qty, v_avail\n    FROM ticketing.ticket_types\n    WHERE id = p_ticket_type_id\n    FOR UPDATE;\n\n    IF NOT FOUND THEN\n        RAISE EXCEPTION 'ticket_type % not found', p_ticket_type_id;\n    END IF;\n\n    v_new_avail := v_avail + p_delta;\n\n    IF v_new_avail &lt; 0 THEN\n        RAISE EXCEPTION 'Cannot reduce below zero';\n    END IF;\n\n    IF v_new_avail &gt; v_qty THEN\n        RAISE EXCEPTION 'Cannot exceed quantity';\n    END IF;\n\n    UPDATE ticketing.ticket_types\n    SET available_quantity = v_new_avail\n    WHERE id = p_ticket_type_id;\nEND;\n$$;\n</code></pre>\n<p>This procedure does several important things:</p>\n<ul>\n<li><strong>Locks the row</strong> with <code>FOR UPDATE</code> so no other transaction can modify it until we're done</li>\n<li><strong>Validates business rules</strong> before making changes</li>\n<li><strong>Provides clear error messages</strong> when something goes wrong</li>\n<li><strong>Keeps everything atomic</strong> in a single database round trip</li>\n</ul>\n<p>You could do all this in C# with manual transaction management and explicit locking, but it's more complex and error-prone.\nLet the database handle what it's good at.</p>\n<p>Here's how you call it from EF Core:</p>\n<pre><code class=\"language-csharp\">app.MapPut(&quot;ticket-types/{ticketTypeId}/available-quantity&quot;, async (\n    Guid ticketTypeId,\n    int quantity,\n    EventManagementContext dbContext) =&gt;\n{\n    try\n    {\n        await dbContext.Database.ExecuteSqlAsync(\n            $&quot;&quot;&quot;\n             CALL ticketing.adjust_available_quantity({ticketTypeId},{quantity})\n             &quot;&quot;&quot;);\n\n        return Results.Ok(result);\n    }\n    catch (Exception e)\n    {\n        return Results.BadRequest(e.Message);\n    }\n});\n</code></pre>\n<p>The procedure doesn't return a value, but if it raises an exception (with <code>RAISE EXCEPTION</code>), PostgreSQL will propagate that to your C# code.\nYou can catch it and return a proper error response.</p>\n<h2>About SQL Injection (Don't Panic)</h2>\n<p>You might be looking at those interpolated strings and thinking &quot;wait, isn't this <a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries\"><strong>SQL injection waiting to happen</strong></a>?&quot;</p>\n<p><strong>It's not</strong>.</p>\n<p>When you write:</p>\n<pre><code class=\"language-csharp\">$&quot;SELECT * FROM users WHERE id = {userId}&quot;\n</code></pre>\n<p>EF Core doesn't concatenate strings.\nIt converts this into:</p>\n<pre><code class=\"language-sql\">SELECT * FROM users WHERE id = @p0\n</code></pre>\n<p><strong>The actual value is sent as a parameter</strong>, completely separate from the SQL text.\nThis works for all the examples in this article.</p>\n<p>The interpolation syntax is just a convenient way to write parameterized queries.</p>\n<p>The reason why is we're not actually passing in a <code>string</code> to the <code>SqlQuery</code> method,\nbut a <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.formattablestring\"><code>FormattableString</code></a>.\nThis is a special type that captures the format and arguments separately, allowing EF Core to handle parameters.</p>\n<p>Everything in this article works with <a href=\"https://learn.microsoft.com/en-us/sql/\">SQL Server</a>,\n<a href=\"https://dev.mysql.com/\">MySQL</a>,\n<a href=\"https://www.sqlite.org/index.html\">SQLite</a>, and other databases EF Core supports.\nThe differences are mostly syntax.</p>\n<h2>A Quick Word on Views</h2>\n<p>Database views are like functions without parameters.\nThey're saved queries you can reference by name.</p>\n<p>You can query them using <code>SqlQuery&lt;T&gt;</code> just like functions:</p>\n<pre><code class=\"language-csharp\">var results = await dbContext.Database\n    .SqlQuery&lt;ActiveCustomerDto&gt;(\n        $&quot;SELECT * FROM ticketing.active_customers&quot;)\n    .ToListAsync();\n</code></pre>\n<p>Or you can map them to entity types in your <code>DbContext</code> for full LINQ support.</p>\n<p>Views are great for frequently-used queries that don't need parameters.\nFunctions give you the flexibility of parameterization.</p>\n<h2>Wrapping Up</h2>\n<p>We've covered how to use PostgreSQL functions and procedures with EF Core, from simple scalar functions to complex procedures with validation and locking.</p>\n<p>You learned when to use functions (when you need data back) versus procedures (when you need to modify data).\nYou saw how EF Core's <code>SqlQuery&lt;T&gt;</code> and <code>ExecuteSqlAsync</code> give you type safety while letting you write the SQL you need.\nAnd you learned when raw SQL makes sense: complex aggregations, database-specific features, atomic operations with locking, and reducing round trips.</p>\n<p><strong>EF Core</strong> doesn't force you to choose between LINQ and raw SQL.\nYou can use both.</p>\n<p>Use functions when you need to return data, procedures when you need to modify data with complex logic,\nand raw SQL queries when LINQ doesn't capture your requirements efficiently.\nThe combination of EF Core's convenience and the database's power gives you the flexibility to choose the right tool for each scenario.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql",
            "title": "Using Stored Procedures and Functions With EF Core and PostgreSQL",
            "summary": "Learn how to use PostgreSQL stored procedures and functions with EF Core to handle complex queries, atomic operations with locking, and database-specific…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_162.png",
            "date_modified": "2025-10-04T00:00:00.000Z",
            "date_published": "2025-10-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-secure-apis-with-role-based-access-control-in-aspnetcore",
            "content_html": "<p>Role-Based Access Control assigns users to roles, and roles carry permissions like <code>users:read</code> or <code>orders:create</code>.\nIn ASP.NET Core you check the permission, not the role name: a custom <code>AuthorizationHandler</code> looks for a matching permission claim and calls <code>context.Succeed</code>.\nRoles can then change without touching code.</p>\n<p>Authentication tells you <strong>who</strong> the user is.\nAuthorization tells you <strong>what</strong> they can do.</p>\n<p>Most .NET developers start with simple role-based checks: &quot;Is this user an Admin?&quot;\nBut as your application grows, you quickly realize that roles alone aren't enough.\nYou need <strong>granular permissions</strong> that can be combined and assigned flexibly.</p>\n<p>That's where <strong>Role-Based Access Control (RBAC)</strong> shines.\nInstead of hardcoding role checks everywhere, you define specific permissions and let roles carry those permissions.\nA user might be a <code>Manager</code> role, but what matters is whether they have the <code>users:delete</code> permission.</p>\n<p>Let me show you how to build a flexible, <strong>permission-based authorization</strong> system in ASP.NET Core.</p>\n<h2>Understanding RBAC Components</h2>\n<p><a href=\"https://auth0.com/docs/manage-users/access-control/rbac\">RBAC</a> has three key components that work together:</p>\n<p><strong>Users</strong> → assigned to → <strong>Roles</strong> → which contain → <strong>Permissions</strong></p>\n<p>Here's how it flows:</p>\n<ul>\n<li><strong>Users</strong>: Individual people using your system</li>\n<li><strong>Roles</strong>: Groups of related permissions (Admin, Manager, Editor)</li>\n<li><strong>Permissions</strong>: Specific actions users can perform (users:read, orders:create, reports:delete)</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_161/rbac.png\" alt=\"Role-based access control mapping users to developer, manager, and admin roles and their permissions\">\n<p>The beauty is in the flexibility.\nYou can assign multiple roles to a user, and roles can be modified without touching user assignments.\nNeed to give all Managers the ability to export reports?\nJust add the <code>reports:export</code> permission to the <code>Manager</code> role.</p>\n<p>This is much more maintainable than checking if someone is specifically an <code>Admin</code> or <code>Super Manager</code> in your code.</p>\n<p>It also adds an extra extension point: you can implement custom permissions for some users without creating new roles.</p>\n<h2>Building a Custom Authorization Handler</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction\">ASP.NET Core's authorization system</a> is built around <strong>policies</strong> and <strong>requirements</strong>.\nLet's create a custom handler that checks permissions stored in the user's claims:</p>\n<pre><code class=\"language-csharp\">public class PermissionAuthorizationRequirement(params string[] allowedPermissions)\n    : AuthorizationHandler&lt;PermissionAuthorizationRequirement&gt;, IAuthorizationRequirement\n{\n    public string[] AllowedPermissions { get; } = allowedPermissions;\n\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        PermissionAuthorizationRequirement requirement)\n    {\n        foreach (var permission in requirement.AllowedPermissions)\n        {\n            bool found = context.User.FindFirst(c =&gt;\n                c.Type == CustomClaimTypes.Permission &amp;&amp;\n                c.Value == permission) is not null;\n\n            if (found)\n            {\n                context.Succeed(requirement);\n                break;\n            }\n        }\n        return Task.CompletedTask;\n    }\n}\n</code></pre>\n<p>Here's what's happening under the hood:</p>\n<p>The class combines both the <strong>requirement</strong> (what permissions are needed) and the <strong>handler</strong> (how to check them).\nThis keeps related logic together and reduces boilerplate.</p>\n<p>The handler looks through the user's claims for any claim with type <code>Permission</code> that matches one of the required permissions.\nIt's an <strong>OR operation</strong> - the user only needs <strong>one</strong> of the specified permissions to proceed.</p>\n<p>If a matching permission is found, we call <code>context.Succeed(requirement)</code> and break out early.\nNo need to check the remaining permissions.</p>\n<p>Alternatively, you could implement an <strong>AND operation</strong> if your use case requires all permissions to be present.</p>\n<p>You'll need to define your custom claim type:</p>\n<pre><code class=\"language-csharp\">public static class CustomClaimTypes\n{\n    public const string Permission = &quot;permission&quot;;\n}\n</code></pre>\n<p>And then you'll use this when issuing <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT tokens</strong></a> or setting up user claims.</p>\n<pre><code class=\"language-csharp\">var permissions = await (\n        from role in dbContext.Roles\n        join permission in dbContext.RolePermissions on role.Id equals permission.RoleId\n        where roles.Contains(role.Name)\n        select permission.Name)\n    .Distinct()\n    .ToArrayAsync();\n\nList&lt;Claim&gt; claims =\n[\n    new(JwtRegisteredClaimNames.Sub, user.Id),\n    new(JwtRegisteredClaimNames.Email, user.Email!),\n    ..roles.Select(r =&gt; new Claim(ClaimTypes.Role, r)),\n    ..permissions.Select(p =&gt; new Claim(CustomClaimTypes.Permission, p))\n];\n\nvar tokenDescriptor = new SecurityTokenDescriptor\n{\n    Subject = new ClaimsIdentity(claims),\n    Expires = DateTime.UtcNow.AddMinutes(configuration.GetValue&lt;int&gt;(&quot;Jwt:ExpirationInMinutes&quot;)),\n    SigningCredentials = credentials,\n    Issuer = configuration[&quot;Jwt:Issuer&quot;],\n    Audience = configuration[&quot;Jwt:Audience&quot;]\n};\n\nvar tokenHandler = new JsonWebTokenHandler();\n\nstring accessToken = tokenHandler.CreateToken(tokenDescriptor);\n</code></pre>\n<h2>Creating Clean APIs with Extension Methods</h2>\n<p>Raw authorization policies work, but they're verbose. Let's create extension methods that make the developer experience much cleaner:</p>\n<pre><code class=\"language-csharp\">public static class PermissionExtensions\n{\n    public static void RequirePermission(\n        this AuthorizationPolicyBuilder builder,\n        params string[] allowedPermissions)\n    {\n        builder.AddRequirements(new PermissionAuthorizationRequirement(allowedPermissions));\n    }\n}\n</code></pre>\n<p>Now you can use this with <strong>Minimal APIs</strong>:</p>\n<pre><code class=\"language-csharp\">public static class Permissions\n{\n    public const string UsersRead = &quot;users:read&quot;;\n    public const string UsersUpdate = &quot;users:update&quot;;\n    public const string UsersDelete = &quot;users:delete&quot;;\n}\n\napp.MapGet(&quot;me&quot;, (ApplicationDbContext dbContext) =&gt;\n{\n    var user = await dbContext.Users\n        .AsNoTracking()\n        .Where(u =&gt; u.Id == int.Parse(User.FindFirstValue(JwtRegisteredClaimNames.Sub)!))\n        .Select(u =&gt; new UserDto\n        {\n            u.Id,\n            u.Email,\n            u.FirstName,\n            u.LastName\n        })\n        .SingleOrDefaultAsync();\n\n    return Results.Ok(user);\n})\n.RequireAuthorization(policy =&gt; policy.RequirePermission(Permissions.UsersRead));\n</code></pre>\n<p>For <strong>MVC Controllers</strong>, create an attribute:</p>\n<pre><code class=\"language-csharp\">[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]\npublic class RequirePermissionAttribute(params string[] permissions) : AuthorizeAttribute\n{\n    public RequirePermissionAttribute(params string[] permissions)\n        : base(policy: string.Join(&quot;,&quot;, permissions))\n    {\n    }\n}\n</code></pre>\n<p>Then register the policy in your DI container:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddAuthorizationBuilder()\n    .AddPolicy(&quot;users:read&quot;, policy =&gt; policy.RequirePermission(Permissions.UsersRead))\n    .AddPolicy(&quot;users:update&quot;, policy =&gt; policy.RequirePermission(Permissions.UsersUpdate));\n</code></pre>\n<p>Usage becomes clean:</p>\n<pre><code class=\"language-csharp\">[RequirePermission(Permissions.UsersUpdate)]\npublic async Task&lt;IActionResult&gt; UpdateUser(int id, UpdateUserRequest request)\n{\n    // Your logic here\n}\n</code></pre>\n<h2>Extension Points for Production</h2>\n<p>The basic implementation works great, but we could improve it further.\nHere are two key extension points:</p>\n<h3>Type-Safe Permissions with Enums</h3>\n<p>Instead of magic strings, use enums for compile-time safety:</p>\n<pre><code class=\"language-csharp\">public enum Permission\n{\n    UsersRead,\n    UsersUpdate,\n    UsersDelete,\n    OrdersCreate,\n    ReportsExport\n}\n</code></pre>\n<p>You'll have to convert these to strings when issuing claims and checking permissions.\nAnd also convert from a string to an enum, when reading from claims and validating the permissions.</p>\n<h3>Server-Side Permission Resolution</h3>\n<p>Rather than storing all permissions in JWT tokens (which can get large), fetch them server-side using <code>IClaimsTransformation</code>:</p>\n<pre><code class=\"language-csharp\">public class PermissionClaimsTransformation(IPermissionService permissionService)\n    : IClaimsTransformation\n{\n    public async Task&lt;ClaimsPrincipal&gt; TransformAsync(ClaimsPrincipal principal)\n    {\n        if (principal.Identity?.IsAuthenticated != true)\n        {\n            return principal;\n        }\n\n        var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;\n        if (userId == null)\n        {\n            return principal;\n        }\n\n        // Fetch permissions from database, then cache\n        // IMPORTANT: Cache these results to avoid DB hits on every request\n        var permissions = await permissionService.GetUserPermissionsAsync(userId);\n\n        var claimsIdentity = (ClaimsIdentity)principal.Identity;\n        foreach (var permission in permissions)\n        {\n            claimsIdentity.AddClaim(new Claim(CustomClaimTypes.Permission, permission));\n        }\n\n        return principal;\n    }\n}\n</code></pre>\n<p>Register it in your DI container:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddScoped&lt;IClaimsTransformation, PermissionClaimsTransformation&gt;();\n</code></pre>\n<p>This approach keeps your JWTs lightweight while still providing fast authorization checks through claims.</p>\n<p>You can learn more about claims transformation in my <a href=\"https://milanjovanovic.tech/blog/master-claims-transformation-for-flexible-aspnetcore-authorization\"><strong>previous article</strong></a>.</p>\n<h2>Takeaway</h2>\n<p><strong>RBAC</strong> transforms authorization from a maintenance headache into a flexible, scalable system.</p>\n<p><strong>Start with permissions</strong>: Define what actions users can perform, not what roles they have.</p>\n<p><strong>Custom authorization handlers</strong> give you complete control over how permissions are validated.</p>\n<p><strong>Extension methods</strong> make the developer experience clean and consistent across your API.</p>\n<p>For production systems, consider <strong>type-safe enums</strong> and <strong>server-side permission resolution</strong> to keep your tokens lean and your code maintainable.</p>\n<p>The result? Authorization logic that's easy to understand, test, and modify as your application evolves.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-secure-apis-with-role-based-access-control-in-aspnetcore",
            "title": "Building Secure APIs with Role-Based Access Control in ASP.NET Core",
            "summary": "Learn how to implement Role-Based Access Control (RBAC) in ASP.NET Core with custom authorization handlers, permission-based policies, and clean extension…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_161.png",
            "date_modified": "2025-09-27T00:00:00.000Z",
            "date_published": "2025-09-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/distributed-locking-in-dotnet-coordinating-work-across-multiple-instances",
            "content_html": "<p>Distributed locking lets one application instance hold a critical section while the others wait, which in-process primitives like <code>lock</code> and <code>SemaphoreSlim</code> cannot do once you scale out.\nIn .NET you can build it on PostgreSQL advisory locks with <code>pg_try_advisory_lock</code>, or use the DistributedLock library with a Postgres, Redis, or SQL Server backend.</p>\n<p>When you build applications that run across multiple servers or processes, you eventually run into the problem of concurrent access.\nMultiple workers try to update the same resource at the same time, and you end up with race conditions, duplicated work, or corrupted data.</p>\n<p>.NET provides excellent <a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6\"><strong>concurrency control primitives</strong></a> for single-process scenarios,\nlike <code>lock</code>, <code>SemaphoreSlim</code>, and <code>Mutex</code>.\nBut when your application is scaled out across multiple instances, these primitives don't work anymore.</p>\n<p>That's where <strong>distributed locking</strong> comes in.</p>\n<p>Distributed locking provides a solution by ensuring <strong>only one node</strong> (application instance) can access a critical section at a time,\npreventing race conditions and maintaining data consistency <strong>across your distributed system</strong>.</p>\n<h2>Why and When You Need Distributed Locking</h2>\n<p>In a single-process app, you can just use <code>lock</code> or the new <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.lock\">Lock class</a> in .NET 10.\nBut once you scale out, that's not enough, because each process has its own memory space.</p>\n<p>A few common cases where distributed locks are valuable:</p>\n<ul>\n<li><strong>Background jobs</strong>: ensuring only one worker processes a particular job or resource at a time.</li>\n<li><strong>Leader election</strong>: choosing a single process to perform periodic work (like applying async database projections).</li>\n<li><strong>Avoiding double execution</strong>: ensuring scheduled tasks don't run multiple times when deployed to multiple instances.</li>\n<li><strong>Coordinating shared resources</strong>: e.g., only one service instance performing a migration or cleanup at a time.</li>\n<li><strong>Cache stampede prevention</strong>: ensuring only one instance refreshes the cache when a given cache key expires.</li>\n</ul>\n<p>The key value: consistency and safety across distributed environments.\nWithout this, you risk duplicate operations, corrupted state, or unnecessary load.</p>\n<p>Now you know why distributed locking is important.</p>\n<p>Let's look at some implementation options.</p>\n<h2>DIY Distributed Locking with PostgreSQL Advisory Locks</h2>\n<p>Let's start simple.\nPostgreSQL has a feature called <a href=\"https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS\">advisory locks</a> that's perfect for distributed locking.\nUnlike table locks, these don't interfere with your data - they're purely for coordination.</p>\n<p>Here's an example:</p>\n<pre><code class=\"language-csharp\">public class NightlyReportService(NpgsqlDataSource dataSource)\n{\n    public async Task ProcessNightlyReport()\n    {\n        await using var connection = dataSource.OpenConnection();\n\n        var key = HashKey(&quot;nightly-report&quot;);\n\n        var acquired = await connection.ExecuteScalarAsync&lt;bool&gt;(\n            &quot;SELECT pg_try_advisory_lock(@key)&quot;,\n            new { key });\n\n        if (!acquired)\n        {\n            throw new ConflictException(&quot;Another instance is already processing the nightly report&quot;);\n        }\n\n        try\n        {\n            await DoWork();\n        }\n        finally\n        {\n            await connection.ExecuteAsync(\n                &quot;SELECT pg_advisory_unlock(@key)&quot;,\n                new { key });\n        }\n    }\n\n    private static long HashKey(string key) =&gt;\n        BitConverter.ToInt64(SHA256.HashData(Encoding.UTF8.GetBytes(key)), 0);\n\n    private static Task DoWork() =&gt; Task.Delay(5000); // Your actual work here\n}\n</code></pre>\n<p>Here's what's happening under the hood.</p>\n<p>First, we convert our lock name into a number.\nPostgreSQL <strong>advisory locks need numeric keys</strong>, so we hash <code>nightly-report</code> into a 64-bit integer.\nEvery node (application instance) must generate the same number for the same string, or this won't work.</p>\n<p>Next, <code>pg_try_advisory_lock()</code> attempts to grab an exclusive lock on that number.\nIt returns <code>true</code> if successful, <code>false</code> if another connection already holds it.\nThis call doesn't block - it tells you immediately whether you got the lock.</p>\n<p>If we get the lock, we do our work.\nIf not, we return a conflict response and let the other instance handle it.</p>\n<p>The <code>finally</code> block ensures we always release the lock, even if something goes wrong.\nPostgreSQL also <strong>automatically releases advisory locks when connections close</strong>, which is a nice safety net.</p>\n<p>SQL Server has a similar feature with <a href=\"https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql\">sp_getapplock</a>.</p>\n<h2>Exploring the DistributedLock Library</h2>\n<p>While the DIY approach works, production applications need more sophisticated features.\nThe <a href=\"https://github.com/madelson/DistributedLock\">DistributedLock</a> library handles the edge cases and\nprovides multiple backend options (Postgres, Redis, SqlServer, etc.).\nYou know I'm a fan of not reinventing the wheel, so this is a great choice.</p>\n<p>Install the package:</p>\n<pre><code class=\"language-powershell\">Install-Package DistributedLock\n</code></pre>\n<p>I'll use the approach with <code>IDistributedLockProvider</code> which works nicely with DI.\nYou can acquire a lock without having to know anything about the underlying infrastructure.\nAll you have to do is register a lock provider implementation in your DI container.</p>\n<p>For example, using Postgres:</p>\n<pre><code class=\"language-csharp\">// Register the distributed lock provider\nbuilder.Services.AddSingleton&lt;IDistributedLockProvider&gt;(\n    (_) =&gt;\n    {\n        return new PostgresDistributedSynchronizationProvider(\n            builder.Configuration.GetConnectionString(&quot;distributed-locking&quot;)!);\n    });\n</code></pre>\n<p>Or if you want to use Redis with the <a href=\"https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/\">Redlock algorithm</a>:</p>\n<pre><code class=\"language-csharp\">// Requires StackExchange.Redis\nbuilder.Services.AddSingleton&lt;IConnectionMultiplexer&gt;(\n    (_) =&gt;\n    {\n        return ConnectionMultiplexer.Connect(\n            builder.Configuration.GetConnectionString(&quot;redis&quot;)!);\n    });\n\n// Register the distributed lock provider\nbuilder.Services.AddSingleton&lt;IDistributedLockProvider&gt;(\n    (sp) =&gt;\n    {\n        var connectionMultiplexer = sp.GetRequiredService&lt;IConnectionMultiplexer&gt;();\n\n        return new RedisDistributedSynchronizationProvider(connectionMultiplexer.GetDatabase());\n    });\n</code></pre>\n<p>The usage is straightforward:</p>\n<pre><code class=\"language-csharp\">// You can also pass in a timeout, where the provider will keep retrying to acquire the lock\n// until the timeout is reached.\nIDistributedSynchronizationHandle? distributedLock = distributedLockProvider\n    .TryAcquireLock(&quot;nightly-report&quot;);\n\n// If we didn't get the lock, the object will be null\nif (distributedLock is null)\n{\n    return Results.Conflict();\n}\n\n// It's important to wrap the lock in a using statement to ensure it's released properly\nusing (distributedLock)\n{\n    await DoWork();\n}\n</code></pre>\n<p>The library handles all the tricky parts: timeouts, retries, and ensuring locks are released even in failure scenarios.</p>\n<p>It also supports many backends (SQL Server, Azure, ZooKeeper, etc.), making it a solid choice for production workloads.</p>\n<h2>Wrapping Up</h2>\n<p><strong>Distributed locking</strong> isn't something you need every day.\nBut when you do, it saves you from subtle, painful bugs that only appear under load or in production.</p>\n<p><strong>Start simple</strong>: if you're already using Postgres, <strong>advisory locks</strong> are a powerful tool.\nI now run that exact pattern in production, and the full implementation (blocking acquire, key hashing, the fail-open decision) is in <a href=\"https://milanjovanovic.tech/blog/postgres-advisory-locks-dotnet\"><strong>distributed locking with Postgres advisory locks</strong></a>.</p>\n<p>For a cleaner developer experience, reach for the <strong>DistributedLock library</strong>.</p>\n<p>Choose the backend that fits your infrastructure (Postgres, Redis, SQL Server, etc.).</p>\n<p>The right lock at the right time ensures your system stays consistent, reliable, and resilient, even across multiple processes and servers.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/distributed-locking-in-dotnet-coordinating-work-across-multiple-instances",
            "title": "Distributed Locking in .NET: Coordinating Work Across Multiple Instances",
            "summary": "Learn how to coordinate work across multiple application instances with distributed locking in .NET, preventing race conditions in scaled-out systems.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_160.png",
            "date_modified": "2025-09-20T00:00:00.000Z",
            "date_published": "2025-09-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/vertical-slice-architecture-is-easier-than-you-think",
            "content_html": "<p>Vertical Slice Architecture organizes .NET code by business feature instead of technical layer.\nEach slice holds everything one feature needs: the request, handler, validation, and endpoint in a single folder.\nYou change one folder to change a feature, instead of editing files spread across <code>Controllers</code>, <code>Services</code>, and <code>Repositories</code>.</p>\n<p>Let's say you need to add an &quot;export user data&quot; <strong>feature</strong> to your .NET application.\nUsers click a button, your system generates their data export, uploads it to cloud storage, and emails them a secure download link.</p>\n<p>In your current <strong>layered architecture</strong> with a <strong>technical folder structure</strong>, you'll probably touch six different folders:\n<code>Controllers</code>, <code>Services</code>, <code>Models</code>, <code>DTOs</code>, <code>Repositories</code>, and <code>Validators</code>.\nYou'll scroll up and down your solution explorer, lose your train of thought,\nand wonder why adding one feature requires editing files scattered across your entire codebase.</p>\n<p>If this sounds familiar, you're not alone.\nMost .NET developers start with the &quot;standard&quot; layered architecture, organizing code by <strong>technical concerns</strong> rather than <strong>business features</strong>.</p>\n<p>But there's a better way: <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>Vertical Slice Architecture</strong></a>.</p>\n<h2>What is Vertical Slice Architecture?</h2>\n<p>Instead of organizing your code by technical layers (<code>Controllers</code>, <code>Services</code>, <code>Repositories</code>),\n<strong>Vertical Slice Architecture</strong> organizes it by <strong>business features</strong>.\nEach feature becomes a <strong>self-contained</strong> &quot;slice&quot; that includes <strong>everything needed for that specific functionality</strong>.</p>\n<p>Think of it this way: traditional <a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure\"><strong>layered architecture</strong></a> is like organizing a library by book size or color,\nwhile <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\"><strong>vertical slices</strong></a> are like organizing by subject.\nWhen you want to learn about history, you don't want to hunt through the entire library, you want all the history books in one place.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_159/vertical_slice_architecture.png\" alt=\"Vertical slices for creating, searching, and archiving products across UI, domain, application, and database layers\">\n</div>\n<p>Let's look at a practical example.</p>\n<h2>The Traditional Approach vs. Vertical Slices</h2>\n<p>Let's look at our data export example.\nHere's how a typical .NET project would structure this feature:</p>\n<p><strong>Traditional Layered Structure:</strong></p>\n<pre><code>📁 Controllers/\n└── UsersController.cs (export endpoint)\n📁 Services/\n├── IDataExportService.cs\n├── DataExportService.cs\n├── ICloudStorageService.cs\n├── CloudStorageService.cs\n├── IEmailService.cs\n└── EmailService.cs\n📁 Models/\n├── ExportDataRequest.cs\n└── ExportDataResponse.cs\n📁 Repositories/\n├── IUserRepository.cs\n└── UserRepository.cs\n</code></pre>\n<p>Now here's the same functionality organized as vertical slices:</p>\n<p><strong>Vertical Slice Structure:</strong></p>\n<pre><code>📁 Features/\n└──📁 Users/\n   └──📁 ExportData/\n      ├── ExportUserData.cs\n      └── ExportUserDataEndpoint.cs\n      📁 Create/\n      └── CreateUser.cs\n      📁 GetById/\n      └── GetUserById.cs\n</code></pre>\n<p>The <code>ExportData</code> folder <strong>contains everything related</strong> to exporting user data: the request, response, business logic, and API endpoint.</p>\n<p>Notice I'm still injecting <code>ICloudStorageClient</code> and <code>IEmailSender</code> rather than putting that logic directly in the handler.\nThese are genuine <a href=\"https://milanjovanovic.tech/blog/cross-cutting-concerns-in-vertical-slice-architecture\"><strong>cross-cutting concerns</strong></a> that <strong>multiple features will use</strong>.\nThe key is distinguishing between 'shared because it should be' vs 'shared because this pattern told me to'.</p>\n<h2>Show Me the Code</h2>\n<p>I organize by domain first (<code>Users</code>), then by feature (<code>ExportData</code>).\nSome teams prefer <code>Features/ExportUserData</code> directly, but I find the domain grouping helps when you have many features.\nRelated features stay visually grouped.</p>\n<p>Here's what our data export <strong>feature slice</strong> looks like using a request, handler, and minimal APIs:</p>\n<p><strong>Features/Users/ExportData/ExportUserData.cs</strong></p>\n<pre><code class=\"language-csharp\">public static class ExportUserData\n{\n    public record Request(Guid UserId) : IRequest&lt;Response&gt;;\n\n    public record Response(string DownloadUrl, DateTime ExpiresAt);\n\n    public class Handler(\n        AppDbContext dbContext,\n        ICloudStorageClient storageClient,\n        IEmailSender emailSender)\n        : IRequestHandler&lt;Request, Response&gt;\n    {\n        public async Task&lt;Response&gt; Handle(Request request, CancellationToken ct = default)\n        {\n            // Get user data\n            var user = await dbContext.Users\n                .Include(u =&gt; u.Orders)\n                .Include(u =&gt; u.Preferences)\n                .FirstOrDefaultAsync(u =&gt; u.Id == request.UserId, ct);\n\n            if (user == null)\n            {\n                throw new NotFoundException($&quot;User {request.UserId} not found&quot;);\n            }\n\n            // Generate export data\n            var exportData = new\n            {\n                user.Email,\n                user.Name,\n                user.CreatedAt,\n                Orders = user.Orders.Select(o =&gt; new { o.Id, o.Total, o.Date }),\n                Preferences = user.Preferences\n            };\n\n            // Upload to cloud storage\n            var fileName = $&quot;user-data-{user.Id}-{DateTime.UtcNow:yyyyMMdd}.json&quot;;\n            var expiresAtUtc = DateTime.UtcNow.AddDays(7);\n\n            var downloadUrl = await storageClient.UploadAsJsonAsync(\n                fileName,\n                exportData,\n                expiresAtUtc,\n                ct);\n\n            // Send email notification\n            await emailSender.SendDataExportEmailAsync(user.Email, downloadUrl, ct);\n\n            return new Response(downloadUrl, expiresAtUtc);\n        }\n    }\n\n    // Simple validation using FluentValidation\n    public sealed class Validator : AbstractValidator&lt;Request&gt;\n    {\n        public Validator()\n        {\n            RuleFor(r =&gt; r.UserId).NotEmpty();\n        }\n    }\n}\n</code></pre>\n<p>Everything related to exporting user data is in one place: the database query, validation, business logic, cloud storage integration, and email notification.</p>\n<p>The minimal API endpoint is straightforward:</p>\n<pre><code class=\"language-csharp\">public static class ExportUserDataEndpoint\n{\n    public static void Map(IEndpointRouteBuilder app)\n    {\n        app.MapPost(&quot;/users/{userId}/export&quot;, async (\n            Guid userId,\n            IRequestHandler&lt;ExportUserData.Request, ExportUserData.Response&gt; handler) =&gt;\n        {\n            var response = await handler.Handle(new ExportUserData.Request(userId));\n            return Results.Ok(response);\n        });\n    }\n}\n</code></pre>\n<p>We could even define the endpoint inside the <code>ExportUserData.cs</code> file if we wanted to keep everything together.\nThis is more a matter of preference and team conventions.\nEither approach works well, from my experience.</p>\n<h2>One File vs. Multiple Files: Your Choice</h2>\n<p>You might have noticed something: I put everything in a single file.\nThis is a design choice with trade-offs.</p>\n<p><strong>Single File Approach (ExportUserData.cs):</strong></p>\n<pre><code class=\"language-csharp\">public static class ExportUserData\n{\n    public record Request(Guid UserId) : IRequest&lt;Response&gt;;\n    public record Response(string DownloadUrl, DateTime ExpiresAt);\n    public class Handler : IRequestHandler&lt;Request, Response&gt; { /* ... */ }\n    public class Validator : AbstractValidator&lt;Request&gt; { /* ... */ }\n}\n</code></pre>\n<p><strong>Multiple Files Approach:</strong></p>\n<pre><code>📁 ExportData/\n├── ExportUserDataCommand.cs\n├── ExportUserDataResponse.cs\n├── ExportUserDataHandler.cs\n├── ExportUserDataValidator.cs\n└── ExportUserDataEndpoint.cs\n</code></pre>\n<p><strong>Single file is great when</strong>: the feature is straightforward, you want <strong>maximum locality</strong>, and the file doesn't exceed a few hundred lines of code.</p>\n<p>Lines of code isn't a strict rule, but if a file grows beyond 300-400 lines, consider splitting it up for readability.\nAgain, this is a matter of team preference and not a hard rule I go by.\nIt's important to trust your instincts and what feels right for your team.</p>\n<p><strong>Multiple files work better when</strong>: you have <a href=\"https://milanjovanovic.tech/blog/validation-vertical-slice-architecture\"><strong>complex validation logic</strong></a>, multiple response types,\nor when the handler grows large enough that you want to focus on one concern at a time.</p>\n<p>You can even mix both approaches within the same project.</p>\n<p>Both approaches keep related code together.\nAnd this is what matters most in Vertical Slice Architecture.</p>\n<h2>Why This Actually Works (And How to Start)</h2>\n<p>The benefits of vertical slices become obvious once you try it.\nYour brain doesn't have to remember which files are related to which features.\nEverything lives together.</p>\n<p>Need to modify the data export feature?\nEverything's in the <code>ExportData</code> folder.\nNo hunting across <code>Controllers</code>, <code>Services</code>, and <code>Repositories</code> layers.\nEach slice can evolve independently, so simple CRUD operations stay simple while complex features like data export can use sophisticated approaches.</p>\n<p>You don't need to rewrite your entire application overnight.\nStart with new features using vertical slices.\nAs you touch existing code, gradually move related pieces into feature folders.</p>\n<p>Good architecture is about making your codebase easier to understand and modify.\nWhen all the code for a feature lives together, you spend less mental energy navigating your solution and more time solving actual problems.</p>\n<p>Here are a few resources if you want to learn more:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/clean-architecture-the-missing-chapter\"><strong>Clean Architecture: The Missing Chapter</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>Modular Monolith Architecture</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/screaming-architecture\"><strong>Screaming Architecture</strong></a></li>\n</ul>\n<p>All of these concepts tie together to help you build maintainable, scalable .NET applications.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/vertical-slice-architecture-is-easier-than-you-think",
            "title": "Vertical Slice Architecture Is Easier Than You Think",
            "summary": "Learn how Vertical Slice Architecture organizes .NET code by business features instead of technical layers, keeping related functionality together and making…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_159.png",
            "date_modified": "2025-09-13T00:00:00.000Z",
            "date_published": "2025-09-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-semantic-search-with-amazon-s3-vectors-and-semantic-kernel",
            "content_html": "<p>Amazon S3 Vectors is a new S3 bucket type built for storing and querying vector embeddings, at 90% lower cost than alternatives according to the AWS announcement.\nIn .NET, you generate embeddings with Semantic Kernel and Amazon Bedrock, then store and query them with the S3 Vectors SDK.\nIt was still in preview when I wrote this.</p>\n<p>I implemented <a href=\"https://milanjovanovic.tech/blog/how-i-implemented-full-text-search-on-my-website\"><strong>full-text search</strong></a> on my static website using Lunr.js.\nIt works great for exact matches or phrases, but it doesn't understand meaning.\nSomeone searches for &quot;modular monolith&quot; and finds posts that contain these phrases.\nBut when they search for &quot;database performance issues,&quot; they might miss my articles about query optimization and index tuning, even though that's exactly what they need.</p>\n<p><a href=\"https://aws.amazon.com/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/\"><strong>Amazon S3 is now also a vector database</strong></a>,\nand it's 90% cheaper (according to the announcement) than the alternatives.</p>\n<p>For those of us running static sites or simple web apps, it means we can finally add semantic search without the operational overhead of\nrunning a vector database like <a href=\"https://www.pinecone.io/\">Pinecone</a>, <a href=\"https://weaviate.io/\">Weaviate</a>, or <a href=\"https://qdrant.tech/\">Qdrant</a>.</p>\n<p>Instead, we can just use S3 buckets that understand vectors.</p>\n<p>I'm adding it alongside my existing full-text search implementation, and the whole thing took an afternoon to build.</p>\n<h2>How Semantic Search Works</h2>\n<p>The entire <a href=\"https://cloud.google.com/discover/what-is-semantic-search\">semantic search</a> flow is straightforward:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_158/semantic_search_flow.png\" alt=\"Semantic search flow\">\n<p>You have to start with some data.\nIn my case, this data comes from the articles I've written over the years.</p>\n<p>Then we need an embedding model.\nAmazon Bedrock offers many to choose from.\nYou use an embedding model to convert text into vectors.\nVectors are numerical representations of your data that capture semantic meaning.</p>\n<p>Finally, we store these vectors in an S3 Vector Bucket, a new type of S3 bucket designed specifically for vector storage and search.\nWhen someone searches, we convert their query into a vector and find the closest matches in the bucket.</p>\n<p>If you want to understand the fundamentals, I wrote an article explaining what <a href=\"https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide\"><strong>vector search</strong></a> is.</p>\n<h2>Generating Embeddings with Semantic Kernel</h2>\n<p>Microsoft's <a href=\"https://learn.microsoft.com/en-us/semantic-kernel/overview/\">Semantic Kernel</a> makes working with Bedrock surprisingly clean.</p>\n<p>We'll need to install a few NuGet packages:</p>\n<pre><code class=\"language-powershell\"># Semantic kernel packages\nInstall-Package Microsoft.SemanticKernel\nInstall-Package Microsoft.SemanticKernel.Connectors.Amazon\n\n# AWS SDK for Bedrock\nInstall-Package AWSSDK.BedrockRuntime\n</code></pre>\n<p>We have to configure the embedding generator in our application.\nHere we can specify which model to use for generating embeddings.\nI'll use the Amazon Titan embedding model (<code>amazon.titan-embed-text-v2:0</code>) which produces 1024-dimensional vectors.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddBedrockEmbeddingGenerator(&quot;amazon.titan-embed-text-v2:0&quot;);\n\nbuilder.Services.AddTransient(sp =&gt;\n{\n    return new Kernel(sp);\n});\n</code></pre>\n<p>And then we can use the <code>IEmbeddingGenerator</code> abstraction to generate embeddings:</p>\n<pre><code class=\"language-csharp\">var kernel = app.Services.GetRequiredService&lt;Kernel&gt;();\n\nvar embeddingGenerator = kernel.Services\n    .GetRequiredService&lt;IEmbeddingGenerator&lt;string, Embedding&lt;float&gt;&gt;&gt;();\n\nvar articleContent = await blogService.GetBlogContentAsync(articleUrl);\n\nEmbedding&lt;float&gt; embedding = await embeddingGenerator.GenerateAsync(articleContent);\n\nembeddings.Add((articleUrl, embedding.Vector.ToArray()));\n</code></pre>\n<p>The number of dimensions varies depending on the embedding model you choose.\nYou can find more information about that in the documentation for your specific model.</p>\n<h2>Creating Your S3 Vector Bucket</h2>\n<p>S3 Vectors uses an entirely new bucket type, not a feature you enable on existing buckets.\nThey're a fundamentally different storage system optimized for vector operations.\nCreating one feels familiar if you've used S3 before:</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_158/s3_vectors_bucket.png\" alt=\"Creating S3 Vectors bucket\">\n</div>\n<p>Pick a unique name for your vector bucket:</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_158/s3_vectors_bucket_create.png\" alt=\"Creating S3 Vectors bucket\">\n</div>\n<p>And finally, you can create a <strong>vector index</strong> for your bucket.\nYou get to choose the number of dimensions in each vector.\nThis is dictated by the embedding model you use.\nAll vectors within a vector index should use the same embedding model.\nOtherwise, you won't get the correct results when searching.\nYou also have to choose the <a href=\"https://en.wikipedia.org/wiki/Metric_space\">distance metric</a> (e.g., cosine similarity, Euclidean distance) for your vector index.\nI went with <a href=\"https://en.wikipedia.org/wiki/Cosine_similarity\">cosine similarity</a>, which is a common choice for text embeddings.\nThe additional settings let you configure non-filterable metadata.\nBy default, all metadata is filterable.</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_158/s3_vectors_index_create.png\" alt=\"Creating S3 Vectors bucket\">\n</div>\n<p>The UI is not polished at all, and you can't do much else.\nI expect this will improve over time, but for now, everything else is done via the SDK or CLI.\nThere are certain limitations to be aware of, you can check out the docs <a href=\"https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-limitations.html\">here</a>.</p>\n<h2>Storing Vectors with Metadata</h2>\n<p>Now that we have our vector index, we can start storing vector embeddings with metadata.\nMetadata is interesting because it enables filtered searches.\nWant to search only recent posts? Posts in a specific category?\nThe metadata makes it possible.</p>\n<p>Here's how you can store vectors:</p>\n<pre><code class=\"language-csharp\">public async Task IndexBlogPost(BlogPost post)\n{\n    List&lt;float&gt; embedding = await GenerateEmbedding(post.Content);\n\n    await s3VectorsClient.PutVectorsAsync(new PutVectorsRequest\n    {\n        VectorBucketName = &quot;mjtech-articles-semantic-search&quot;,\n        IndexName = &quot;mjtech-article-content&quot;,\n        Vectors = new List&lt;Vector&gt;\n        {\n            new PutInputVector\n            {\n                Key = post.Slug,\n                Data = new VectorData\n                {\n                    Float32 = embedding\n                },\n                Metadata = new Document(new Dictionary&lt;string, Document&gt;\n                {\n                    [&quot;title&quot;] = post.Title,\n                    [&quot;date&quot;] = post.PublishedDate.ToString(&quot;yyyy-MM-dd&quot;),\n                    [&quot;category&quot;] = post.Category,\n                    [&quot;url&quot;] = $&quot;/posts/{post.Slug}&quot;\n                })\n            }\n        }\n    });\n}\n</code></pre>\n<p>But if you're indexing your entire blog archive, doing it one post at a time is costly.\nYou can batch together multiple posts and index them in a single API call.\nThe SDK makes it easy to do this by accepting a list of vectors.</p>\n<h2>Querying Vector Indexes</h2>\n<p>When someone searches your site, you convert their query into a vector and find the closest matches.</p>\n<p>Here's how you can implement semantic search:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;List&lt;SearchResult&gt;&gt; SemanticSearch(\n    string query,\n    int topK = 10)\n{\n    // Convert search query to vector (use same model as vectors!)\n    List&lt;float&gt; queryEmbedding = await GenerateEmbedding(query);\n\n    var request = new QueryVectorsRequest\n    {\n        VectorBucketName = &quot;mjtech-articles-semantic-search&quot;,\n        IndexName = &quot;mjtech-article-content&quot;,\n        QueryVector = new VectorData\n        {\n            Float32 = queryEmbedding\n        },\n        TopK = topK,\n        ReturnMetadata = true,\n        ReturnDistance = true\n    };\n\n    QueryVectorsResponse response = await s3VectorsClient.QueryVectorsAsync(request);\n\n    return response.Vectors.Select(v =&gt; new SearchResult\n    {\n        Distance = v.Distance,\n        Title = v.Metadata.AsDictionary()[&quot;title&quot;].ToString(),\n        Url = v.Metadata.AsDictionary()[&quot;url&quot;].ToString(),\n        Category = v.Metadata.AsDictionary()[&quot;category&quot;].ToString()\n    }).ToList();\n}\n</code></pre>\n<p>I omitted metadata filtering here, but you can explore the <a href=\"https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-metadata-filtering.html\">documentation</a>\nfor more details.</p>\n<h2>Next Steps</h2>\n<p>Would I migrate from an existing vector database?\nProbably not if everything's working.\nThe operational overhead would have to justify the switch.</p>\n<p>But if you're adding semantic search for the first time, or your vector database bills are getting uncomfortable, S3 Vectors is a viable choice.\nThe setup takes an afternoon, the ongoing maintenance is zero, and your users get search that actually understands what they're looking for.</p>\n<p>Don't forget that S3 Vectors is still in preview, so we can expect some changes before general availability.</p>\n<p>Here's what I'm planning to do next:</p>\n<ol>\n<li>Automatically update the vector index when I publish a new post. I can do this in my CI/CD pipeline, where I can detect new posts and trigger a re-indexing.</li>\n<li>Expose a search endpoint that uses the new semantic search capabilities. Combine the results with the full-text search results.</li>\n<li>Make sure everything is performant and cost-effective. Acceptable latency is under 500ms.</li>\n<li>Share details about the implementation and any challenges faced. Especially around the costs of using an embedding model and vector storage.</li>\n</ol>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-semantic-search-with-amazon-s3-vectors-and-semantic-kernel",
            "title": "Building Semantic Search with Amazon S3 Vectors and Semantic Kernel",
            "summary": "Learn how to add semantic search to your website using Amazon S3 Vectors - a new vector database service that's 90% cheaper than alternatives.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_158.png",
            "date_modified": "2025-09-06T00:00:00.000Z",
            "date_published": "2025-09-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/standalone-aspire-dashboard-setup-for-distributed-dotnet-applications",
            "content_html": "<p>The Aspire Dashboard ships as a standalone container image, <code>mcr.microsoft.com/dotnet/aspire-dashboard</code>, so you can drop it into Docker Compose without adopting Aspire orchestration.\nServices export telemetry to it over OTLP on port <code>18889</code>, and the UI runs on <code>18888</code>.\nStorage is in memory only, so it is meant for local development and debugging.</p>\n<p>You've built a distributed .NET application.\nMultiple services, databases, message queues.\nNow something's slow, and you need to figure out why.</p>\n<p><strong>The Aspire Dashboard runs perfectly as a standalone container</strong>,\ngiving you <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>distributed tracing</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging\"><strong>structured logs</strong></a>,\nand real-time metrics without the full orchestration framework.</p>\n<p>While <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>Aspire's orchestration</strong></a> is incredibly powerful for managing distributed applications,\nsometimes you just need the observability piece.\nMaybe you're already using <a href=\"https://milanjovanovic.tech/blog/using-dotnet-aspire-with-the-docker-publisher\"><strong>Docker Compose</strong></a> or <a href=\"https://doineedkubernetes.com/\">Kubernetes</a>.\nMaybe you're debugging an existing system.\nThe standalone dashboard gives you valuable telemetry visualization with minimal setup.</p>\n<p>Let's get it running in under 5 minutes.</p>\n<h2>Why Run the Aspire Dashboard Standalone?</h2>\n<p>Most teams already have their deployment story figured out.\nDocker Compose, Kubernetes, or some platform-specific orchestration.\nYou don't want to rewrite everything just to get observability.</p>\n<p>The standalone <strong>Aspire Dashboard</strong> hits a sweet spot <strong>for development</strong>:</p>\n<ul>\n<li><strong>Drop-in observability</strong> - Just add a container to your existing setup</li>\n<li><strong>Full OpenTelemetry support</strong> - Works with any OTLP-compatible application</li>\n<li><strong>Developer-friendly</strong> - Designed for local development and debugging</li>\n<li><strong>Immediate value</strong> - See traces, logs, and metrics within minutes</li>\n</ul>\n<p>One caveat: it's <strong>in-memory only</strong>.\nPerfect for development and debugging, not for production.\nFor production, you'll want something like <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>Jaeger</strong></a>,\n<a href=\"https://prometheus.io/\">Prometheus</a>, or a commercial APM solution.</p>\n<p>But for understanding what your code is doing right now?\nIt's exactly what you need.</p>\n<h2>Step 1: Add the Dashboard Container</h2>\n<p>Drop this into your <code>docker-compose.yml</code>:</p>\n<pre><code class=\"language-yaml\">aspire-dashboard:\n  container_name: aspire-dashboard\n  image: mcr.microsoft.com/dotnet/aspire-dashboard:13.0\n  ports:\n    - 18888:18888\n</code></pre>\n<p>That's it. The dashboard is running.\nNavigate to <code>http://localhost:18888</code> and... you'll need a token.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/aspire_dashboard_login.png\" alt=\"Aspire Dashboard login screen\">\n<p><strong>Check the container logs</strong> for the login link.\nThe dashboard generates a unique authentication token on startup:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/aspire_dashboard_login_link.png\" alt=\"Aspire Dashboard login link\">\n<p>Click that link, and you're in.\nEmpty for now, but not for long.</p>\n<h2>Step 2: Wire Up Your .NET Services</h2>\n<p>Your services need to know where to send their telemetry.\nAdd these environment variables to your API containers:</p>\n<pre><code class=\"language-yaml\">users.api:\n  image: ${DOCKER_REGISTRY-}usersapi\n  build:\n    context: .\n    dockerfile: Users.Api/Dockerfile\n  ports:\n    - 5100:5100\n    - 5101:5101\n  environment:\n    - OTEL_EXPORTER_OTLP_ENDPOINT=http://aspire-dashboard:18889\n    - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n  depends_on:\n    - users.database\n</code></pre>\n<p>Notice port <code>18889</code>?\nThat's the OTLP ingestion endpoint.\nThe dashboard listens on <code>18888</code> for the UI, <code>18889</code> for telemetry data.</p>\n<h2>Step 3: Configure OpenTelemetry in Your Code</h2>\n<p>Install the necessary <a href=\"https://www.nuget.org/packages?q=OpenTelemetry\">OpenTelemetry packages</a>:</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;Npgsql.OpenTelemetry&quot; Version=&quot;9.0.3&quot; /&gt;\n&lt;PackageReference Include=&quot;OpenTelemetry.Exporter.OpenTelemetryProtocol&quot; Version=&quot;1.12.0&quot; /&gt;\n&lt;PackageReference Include=&quot;OpenTelemetry.Extensions.Hosting&quot; Version=&quot;1.12.0&quot; /&gt;\n&lt;PackageReference Include=&quot;OpenTelemetry.Instrumentation.AspNetCore&quot; Version=&quot;1.12.0&quot; /&gt;\n&lt;PackageReference Include=&quot;OpenTelemetry.Instrumentation.Http&quot; Version=&quot;1.12.0&quot; /&gt;\n</code></pre>\n<p>Then <a href=\"https://milanjovanovic.tech/blog/opentelemetry-dotnet-guide\"><strong>configure OpenTelemetry</strong></a> in your <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddOpenTelemetry()\n    .ConfigureResource(resource =&gt; resource.AddService(builder.Environment.ApplicationName))\n    .WithTracing(tracing =&gt; tracing\n        .AddHttpClientInstrumentation()\n        .AddAspNetCoreInstrumentation()\n        .AddNpgsql())\n    .WithMetrics(metrics =&gt; metrics\n        .AddHttpClientInstrumentation()\n        .AddAspNetCoreInstrumentation());\n\nbuilder.Logging.AddOpenTelemetry(options =&gt;\n{\n    options.IncludeScopes = true;\n    options.IncludeFormattedMessage = true;\n});\n\nbuilder.Services.AddOpenTelemetry().UseOtlpExporter();\n</code></pre>\n<p>This configuration:</p>\n<ul>\n<li><strong>Traces</strong> HTTP calls, ASP.NET Core requests, and database queries</li>\n<li><strong>Collects metrics</strong> on request duration, response codes, and throughput</li>\n<li><strong>Structured logging</strong> with full context and formatted messages</li>\n<li><strong>Exports everything</strong> to the Aspire Dashboard via OTLP</li>\n</ul>\n<p>The <code>UseOtlpExporter()</code> method automatically picks up the <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> environment variable you configured earlier.</p>\n<h2>What You Get</h2>\n<p>Start your application and make a few requests.\nThe dashboard immediately lights up with data.</p>\n<h3>Structured Logs</h3>\n<p>Every log entry includes full context: trace IDs, request paths, user identities.\nClick any log to see the complete structured data.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/structured_logs.png\" alt=\"Aspire Dashboard structured logs\">\n<h3>Distributed Traces</h3>\n<p>See the complete request flow across all your services.\nWhich database query is slow? Which HTTP call is failing?\nThe trace view shows you exactly where time is spent.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/distributed_traces.png\" alt=\"Aspire Dashboard distributed traces\">\n<p>You can click into a trace to see the individual spans and any metadata associated with them.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/distributed_trace_details.png\" alt=\"Aspire Dashboard distributed trace details\">\n<h3>Real-Time Metrics</h3>\n<p>Response times, error rates, throughput, all updating live.\nPerfect for load testing or understanding traffic patterns.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_157/metrics.png\" alt=\"Aspire Dashboard metrics\">\n<h2>Summary</h2>\n<p>The standalone <strong>Aspire Dashboard</strong> is perfect for local development and debugging.\nSpin up your stack, make requests, and instantly see what's happening across all your services.\nFind bottlenecks in the trace view, correlate logs with requests, watch metrics update in real-time.</p>\n<p>Remember: this is for development only since data is in-memory and disappears on restart.\nThat last part might be fixed soon, according to the <a href=\"https://youtu.be/zvBu0OOCVos\"><strong>Aspire roadmap</strong></a>.\nFor production, you'll want proper solutions like Jaeger for tracing, Prometheus for metrics, or a commercial APM like Application Insights.</p>\n<p>But for that immediate &quot;what is my code actually doing?&quot; question during development?\nYou've got professional observability in under 5 minutes.</p>\n<p>Just add the container, configure OpenTelemetry, and start debugging like a pro.</p>\n<p>That's all for today.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/standalone-aspire-dashboard-setup-for-distributed-dotnet-applications",
            "title": "Standalone Aspire Dashboard Setup for Distributed .NET Applications",
            "summary": "Learn how to run the Aspire Dashboard as a standalone container for instant traces, logs, and metrics in your .NET applications.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_157.png",
            "date_modified": "2025-08-30T00:00:00.000Z",
            "date_published": "2025-08-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet",
            "content_html": "<p>Abstractions in .NET pay off when they isolate genuine volatility, like payment providers, external APIs, and third-party SDKs.\nThey turn into technical debt when they wrap stable mechanics such as EF Core's LINQ or <code>HttpClient</code> configuration.\nThe test: if removing the layer makes the code simpler, it was costing more than it earned.</p>\n<p>As developers, we love abstractions.\nRepositories, services, mappers, wrappers.\nThey make our code look &quot;clean,&quot; they promise testability, and they give us the sense that we're building something flexible.</p>\n<p><strong>Every abstraction is a loan.\nYou pay interest the moment you write it.</strong></p>\n<p>Some abstractions earn their keep by isolating real volatility and protecting your system from change.\nOthers quietly pile up complexity, slow down onboarding, and hide performance problems behind layers of indirection.</p>\n<p>Let's explore when abstractions pay dividends and when they become technical debt.</p>\n<h2>When Abstractions Pay Off</h2>\n<p>The best abstractions <strong>isolate true volatility</strong>, the parts of your system that you genuinely expect to change.</p>\n<h3>Example: Payment Processing</h3>\n<p>Your core business logic shouldn't depend directly on Stripe's SDK.\nIf you ever switch to Adyen or Braintree, you don't want that decision rippling through every corner of your codebase.\nHere, an abstraction makes perfect sense:</p>\n<pre><code class=\"language-csharp\">public interface IPaymentProcessor\n{\n    Task ProcessAsync(Order order, CancellationToken ct);\n}\n\npublic class StripePaymentProcessor : IPaymentProcessor\n{\n    public async Task ProcessAsync(Order order, CancellationToken ct)\n    {\n        // Stripe-specific implementation\n        // Handle webhooks, error codes, etc.\n    }\n}\n\npublic class AdyenPaymentProcessor : IPaymentProcessor\n{\n    public async Task ProcessAsync(Order order, CancellationToken ct)\n    {\n        // Adyen-specific implementation\n        // Different API, same business outcome\n    }\n}\n</code></pre>\n<p>Now your business logic can stay focused on the domain:</p>\n<pre><code class=\"language-csharp\">public class CheckoutService(IPaymentProcessor processor)\n{\n    public Task CheckoutAsync(Order order, CancellationToken cancellationToken) =&gt;\n        processor.ProcessAsync(order, cancellationToken);\n}\n</code></pre>\n<p>This abstraction isolates a genuinely unstable dependency (the payment provider) while keeping checkout logic independent.\nWhen Stripe changes their API or you switch providers, only one class needs to change.</p>\n<p><strong>That's a good abstraction</strong>.\nIt buys you optionality where you actually need it.</p>\n<h2>When Abstractions Become Technical Debt</h2>\n<p>Problems arise when we abstract things that aren't actually volatile.\nWe end up wrapping stable libraries or creating layers that don't add real value.\nThe &quot;clean&quot; layer you added today becomes tomorrow's maintenance burden.</p>\n<h3>The Repository That Lost Its Way</h3>\n<p>Most teams start with something reasonable:</p>\n<pre><code class=\"language-csharp\">public interface IUserRepository\n{\n    Task&lt;IEnumerable&lt;User&gt;&gt; GetAllAsync();\n}\n</code></pre>\n<p>But as requirements evolve, so does the interface:</p>\n<pre><code class=\"language-csharp\">public interface IUserRepository\n{\n    Task&lt;IEnumerable&lt;User&gt;&gt; GetAllAsync();\n    Task&lt;User?&gt; GetByEmailAsync(string email);\n    Task&lt;IEnumerable&lt;User&gt;&gt; GetActiveUsersAsync();\n    Task&lt;IEnumerable&lt;User&gt;&gt; GetUsersByRoleAsync(string role);\n    Task&lt;IEnumerable&lt;User&gt;&gt; SearchAsync(string keyword, int page, int pageSize);\n    Task&lt;IEnumerable&lt;User&gt;&gt; GetUsersWithRecentActivityAsync(DateTime since);\n    // ...and it keeps growing\n}\n</code></pre>\n<p>Suddenly, the repository is leaking <strong>query logic into its interface</strong>.\nEvery new way of fetching users means another method, and your &quot;abstraction&quot; becomes a grab bag of every possible query.</p>\n<p>Meanwhile, <a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>Entity Framework</strong></a> already gives you all of this through LINQ: strongly typed queries that map directly to SQL.\nInstead of leveraging that power, you've introduced an indirection layer that hides query performance characteristics and often performs worse.\nThe repository pattern made sense when ORMs were immature.\nToday, it's often just ceremony.</p>\n<p>I've been guilty of this myself.\nBut part of maturing as a developer is recognizing when patterns become <a href=\"https://milanjovanovic.tech/blog/clean-architecture-anti-patterns\"><strong>anti-patterns</strong></a>.\nRepositories make sense when they encapsulate complex query logic or provide a unified API over multiple data sources.\nBut you should strive to keep them focused on domain logic.\nAs soon as they explode into a myriad of methods for every possible query, it's a sign that the abstraction has failed.</p>\n<h2>Service Wrappers: The Good and The Ugly</h2>\n<p>Not all service wrappers are problematic. Context matters.</p>\n<p><strong>✅ Good Example: External API Integration</strong></p>\n<p>When integrating with external APIs, a wrapper provides genuine value by centralizing concerns:</p>\n<pre><code class=\"language-csharp\">public interface IGitHubClient\n{\n    Task&lt;UserDto?&gt; GetUserAsync(string username);\n    Task&lt;IReadOnlyList&lt;RepoDto&gt;&gt; GetRepositoriesAsync(string username);\n}\n\npublic class GitHubClient(HttpClient httpClient) : IGitHubClient\n{\n    public Task&lt;UserDto?&gt; GetUserAsync(string username) =&gt;\n        httpClient.GetFromJsonAsync&lt;UserDto&gt;($&quot;/users/{username}&quot;);\n\n    public Task&lt;IReadOnlyList&lt;RepoDto&gt;&gt; GetRepositoriesAsync(string username) =&gt;\n        httpClient.GetFromJsonAsync&lt;IReadOnlyList&lt;RepoDto&gt;&gt;($&quot;/users/{username}/repos&quot;);\n}\n</code></pre>\n<p>This wrapper isolates GitHub's API details.\nWhen authentication changes or endpoints evolve, you update one place.\nYour business logic never needs to know about HTTP headers, base URLs, or JSON serialization.</p>\n<p><strong>❌ Bad Example: Pass-Through Services</strong></p>\n<p>The trouble starts when we wrap our own stable services without adding business value:</p>\n<pre><code class=\"language-csharp\">public class UserService(IUserRepository userRepository)\n{\n    // Just forwarding calls with no added value\n    public Task&lt;User?&gt; GetByIdAsync(Guid id) =&gt; userRepository.GetByIdAsync(id);\n    public Task&lt;IEnumerable&lt;User&gt;&gt; GetAllAsync() =&gt; userRepository.GetAllAsync();\n    public Task SaveAsync(User user) =&gt; userRepository.SaveAsync(user);\n}\n</code></pre>\n<p>This <code>UserService</code> is pure indirection.\nAll it does is forward calls to the <code>IUserRepository</code>.\nIt doesn't enforce business rules, add validation, implement caching, or provide any real functionality.\nIt's a layer that exists only because &quot;services are good architecture.&quot;</p>\n<p>As these anemic wrappers multiply, your codebase becomes a maze.\nDevelopers waste time navigating layers instead of focusing on where business logic actually lives.</p>\n<h2>Making Better Decisions</h2>\n<p>Here's how to think about when abstractions are worth the investment:</p>\n<h3>Abstract Policies, Not Mechanics</h3>\n<ul>\n<li><strong>Policies</strong> are decisions that might change: which payment provider to use, how to handle caching, retry strategies for external calls</li>\n<li><strong>Mechanics</strong> are stable implementation details: EF Core's LINQ syntax, <code>HttpClient</code> configuration, JSON serialization</li>\n</ul>\n<p>Abstract policies because they give you flexibility.\nDon't abstract mechanics, they're already stable APIs that rarely change in breaking ways.</p>\n<h3>Wait for the Second Implementation</h3>\n<p>If you only have one implementation, resist the interface urge.\nA single implementation doesn't justify abstraction, it's premature generalization that adds complexity without benefit.</p>\n<p>Consider this evolution:</p>\n<pre><code class=\"language-csharp\">// Step 1: Start concrete\npublic class EmailNotifier\n{\n    public async Task SendAsync(string to, string subject, string body)\n    {\n        // SMTP implementation\n    }\n}\n\n// Step 2: Need SMS? Now abstract\npublic interface INotifier\n{\n    Task SendAsync(string to, string subject, string body);\n}\n\npublic class EmailNotifier : INotifier { /* ... */ }\npublic class SmsNotifier : INotifier { /* ... */ }\n</code></pre>\n<p>The abstraction emerges naturally when you actually need it.\nThe interface reveals itself through real requirements, not imaginary ones.</p>\n<h3>Keep Implementations Inside, Abstractions at Boundaries</h3>\n<p>Inside your application, prefer <strong>concrete types</strong>.\nUse Entity Framework directly, configure <code>HttpClient</code> as typed clients, work with domain entities.\nOnly introduce abstractions where your system meets the outside world: external APIs, third-party SDKs, infrastructure services.</p>\n<p>That's where change is most likely, and where abstractions earn their keep.</p>\n<h2>Refactoring Out Bad Abstractions</h2>\n<p>Regularly review your abstractions with this question: If I removed this abstraction, would the code become simpler or more complex?</p>\n<p>If removing an interface or service layer would make the code clearer and more direct,\nthat abstraction is probably costing more than it's worth.\nDon't be afraid to delete unnecessary layers.\nSimpler code is often better code.</p>\n<p>When you identify problematic abstractions, here's how to safely remove them:</p>\n<ol>\n<li><strong>Identify the real consumers</strong>. Who actually needs the abstraction?</li>\n<li><strong>Inline the interface</strong>. Replace abstract calls with concrete implementations.</li>\n<li><strong>Delete the wrapper</strong>. Remove the unnecessary indirection.</li>\n<li><strong>Simplify the calling code</strong>. Take advantage of the concrete API's features.</li>\n</ol>\n<p>For example, replacing a repository with direct EF Core usage:</p>\n<pre><code class=\"language-csharp\">// Before: Hidden behind repository\nvar users = await _userRepo.GetActiveUsersWithRecentOrders();\n\n// After: Direct, optimized query\nvar users = await _context.Users\n    .Where(u =&gt; u.IsActive)\n    .Where(u =&gt; u.Orders.Any(o =&gt; o.CreatedAt &gt; DateTime.Now.AddDays(-30)))\n    .Include(u =&gt; u.Orders.Take(5))\n    .ToListAsync();\n</code></pre>\n<p>The concrete version is more explicit about what data it fetches and how, making performance characteristics visible and optimization possible.\nIf you need the same query in multiple places, you could move it into an extension method to make it shareable.</p>\n<h2>The Bottom Line</h2>\n<p>Abstractions are powerful tools for managing complexity and change, but they're not free.\nEach one adds indirection, cognitive overhead, and maintenance burden.</p>\n<p>The <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>cleanest architecture</strong></a> isn't the one with the most layers.\nIt's the one where each layer has a clear, justified purpose.</p>\n<p>Before adding your next abstraction, ask yourself:</p>\n<ul>\n<li>Am I abstracting a policy or just a mechanic?</li>\n<li>Do I have two implementations, or am I speculating about future needs?</li>\n<li>Will this make my system more adaptable, or just harder to follow?</li>\n<li>If I removed this layer, would the code become simpler?</li>\n</ul>\n<p>Remember: abstractions are loans that accrue interest over time.\nMake sure you're borrowing for the right reasons, not just out of habit.</p>\n<p>The goal is to use abstractions intentionally, where they solve real problems and protect against genuine volatility.\nBuild abstractions that earn their keep.\nDelete the ones that don't.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet",
            "title": "The Real Cost of Abstractions in .NET",
            "summary": "Not all abstractions are created equal. Some isolate real volatility and protect your system from change.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_156.png",
            "date_modified": "2025-08-23T00:00:00.000Z",
            "date_published": "2025-08-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-generative-ai-applications-with-github-models-and-dotnet-aspire",
            "content_html": "<p>GitHub Models gives you OpenAI, Microsoft, and Meta models behind one API, and .NET Aspire 9.4 added an integration for it.\nYou declare the model in the AppHost with <code>AddGitHubModel</code>, reference it from your project, and call <code>AddAzureChatCompletionsClient</code> and <code>AddChatClient</code> in the consuming service.\nAspire wires the API key and connection by resource name.</p>\n<p>I wanted to see what the simplest practical AI app I could build was, and this is what I came up with.</p>\n<p>Every week, I publish blog posts covering different topics - architecture patterns, cloud services, programming techniques, business insights.\nSometimes I write about DevOps, other times about security.\nAfter years of writing, I realized I had no systematic way to categorize my content.\nSure, I could manually tag each post, but where's the fun in that?</p>\n<p>So I built a simple AI-powered blog analyzer.\nIt fetches any blog post, extracts the content, and uses AI to automatically categorize it.\nThe entire thing took less than an hour to build thanks to <strong>.NET Aspire 9.4</strong> and it's new <strong>GitHub Models</strong> integration.</p>\n<p>What surprised me wasn't just how easy it was to build, but how the integration completely removes the typical AI service complexity.\nNo juggling API keys in configuration files, no manual HTTP client setup, no wrestling with different SDK patterns for different AI providers.\nYou declare an AI model in your AppHost just like you would a database, and Aspire handles the rest.</p>\n<p>Here's what I learned building this simple app, and how you can use the same patterns to add AI to your applications.</p>\n<h2>What are GitHub Models?</h2>\n<p><a href=\"https://docs.github.com/en/github-models\">GitHub Models</a> is a service that provides access to AI models from OpenAI, Microsoft, Meta, and others through a single API.\nYou get free tier access for prototyping (with rate limits), an interactive playground for testing prompts, and pay-per-use billing when you're ready for production.</p>\n<p>The models range from cost-effective options like <strong>GPT-4o-mini</strong> to more advanced models.\nEach model has different strengths - some excel at reasoning, others at code generation or creative writing.</p>\n<p>When you combine GitHub Models with .NET Aspire's orchestration, you get:</p>\n<ul>\n<li>Automatic API key management via <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/external-parameters\">external parameters</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery\"><strong>Service discovery</strong></a> between your components</li>\n<li>Built-in <a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core\"><strong>health checks</strong></a> and <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>telemetry</strong></a></li>\n<li>Consistent configuration patterns</li>\n</ul>\n<h2>Setting Up the Integration</h2>\n<p>The GitHub Models integration splits into two parts: configuring models in your <code>AppHost</code> and consuming them in your services.</p>\n<h3>AppHost Configuration</h3>\n<p>In your AppHost project, you define AI models as resources alongside your other services:</p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\nvar blogService = builder.AddExternalService(&quot;dotnet-blog&quot;, &quot;https://www.milanjovanovic.tech/blog&quot;);\nvar aiModel = builder.AddGitHubModel(&quot;ai-model&quot;, &quot;openai/gpt-4o-mini&quot;);\n\nbuilder.AddProject&lt;Projects.GitHub_Models_Demo&gt;(&quot;github-models-demo&quot;)\n    .WithReference(blogService)\n    .WithReference(aiModel);\n</code></pre>\n<p>Notice how the AI model sits alongside the external blog service.\nBoth are resources that your main application depends on.\nAspire handles the connection details - you just declare what you need.</p>\n<p>Make sure to install the <a href=\"https://www.nuget.org/packages/Aspire.Hosting.GitHub.Models\">Aspire.Hosting.GitHub.Models</a> NuGet package to enable this integration.</p>\n<p>To call the GitHub Models inference API you need a personal access token with the <code>models:read</code> permission.\nWhen you call <code>AddGitHubModel</code>, Aspire automatically creates a parameter named <code>{resourceName}-gh-apikey</code> (for example, <code>ai-model-gh-apikey</code>)</p>\n<p>You can populate the parameter through user secrets for local development:</p>\n<pre><code class=\"language-json\">{\n  &quot;Parameters&quot;: {\n    &quot;ai-model-gh-apikey&quot;: &quot;github_pat_YOUR_PERSONAL_ACCESS_TOKEN&quot;\n  }\n}\n</code></pre>\n<p>If you don't provide this value from configuration, Aspire will prompt you to enter it when you run the application.\nYou'll have an option to store the access token securely in user secrets.</p>\n<h3>Client Setup</h3>\n<p>In your consuming service, add the Azure AI client (which works with GitHub Models):</p>\n<pre><code class=\"language-csharp\">builder\n    .AddAzureChatCompletionsClient(&quot;ai-model&quot;)\n    .AddChatClient();\n</code></pre>\n<p>That's it.\nNo manual HTTP client configuration, no hardcoded endpoints.\nThe <code>ai-model</code> name matches what you defined in the AppHost, and Aspire wires everything together.</p>\n<p>You'll need to install the <a href=\"https://www.nuget.org/packages/Aspire.Azure.AI.Inference\">Aspire.Azure.AI.Inference</a> NuGet package to enable this integration.\nIt also exposes an integration with <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><strong>MEAI</strong></a> using the <code>AddChatClient</code> method.\nThis simplifies the process of interacting with LLMs in your applications.</p>\n<h2>Building the Blog Analyzer</h2>\n<p>Now let's build something useful.\nWe'll create a service that fetches blog posts and uses AI to categorize them.</p>\n<h3>Fetching Blog Content</h3>\n<p>First, we need to extract readable content from blog posts:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;string&gt; GetBlogContentAsync(string slug)\n{\n    var response = await httpClient.GetAsync(slug);\n    response.EnsureSuccessStatusCode();\n    var htmlContent = await response.Content.ReadAsStringAsync();\n\n    return ExtractArticleContent(htmlContent);\n}\n</code></pre>\n<p>The <code>ExtractArticleContent</code> method (not shown) uses <a href=\"https://www.nuget.org/packages/htmlagilitypack/\">HtmlAgilityPack</a> to pull out the main article text,\nstripping away navigation, ads, and other page elements.</p>\n<h3>AI-Powered Categorization</h3>\n<p>Here's where it gets interesting.\nWe'll use the AI model to analyze the content and assign a category:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;string&gt; SummarizeBlogAsync(string blogContent)\n{\n    var prompt =\n        @&quot;&quot;&quot;\n        You are a blog content assistant. Summarize the following blog post\n        as one of the following categories: Technology, Business, Programming,\n        Architecture, DevOps, Cloud, Security, General.\n        Only those eight values are allowed. Be as concise as possible.\n        I want a 1-word response with one of these options: Technology, Business,\n        Programming, Architecture, DevOps, Cloud, Security, General.\n\n        The blog content is: {blogContent}\n        &quot;&quot;&quot;;\n\n    var response = await chatClient.GetResponseAsync(prompt);\n\n    if (!response.Messages.Any())\n    {\n        return &quot;General&quot;;\n    }\n\n    var category = response.Messages.First().Text switch\n    {\n        var s when s.Contains(&quot;Technology&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Technology&quot;,\n        var s when s.Contains(&quot;Business&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Business&quot;,\n        var s when s.Contains(&quot;Programming&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Programming&quot;,\n        var s when s.Contains(&quot;Architecture&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Architecture&quot;,\n        var s when s.Contains(&quot;DevOps&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;DevOps&quot;,\n        var s when s.Contains(&quot;Cloud&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Cloud&quot;,\n        var s when s.Contains(&quot;Security&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;Security&quot;,\n        var s when s.Contains(&quot;General&quot;, StringComparison.OrdinalIgnoreCase) =&gt; &quot;General&quot;,\n        _ =&gt; &quot;General&quot;\n    };\n\n    return category;\n}\n</code></pre>\n<p>A few things make this work reliably:</p>\n<ul>\n<li>The prompt is explicit about allowed categories</li>\n<li>We request a single-word response to avoid parsing complex output</li>\n<li>The switch expression handles variations in the AI's response</li>\n<li>There's always a fallback to &quot;General&quot;</li>\n</ul>\n<h3>Exposing the API</h3>\n<p>Finally, we wrap everything in a simple <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>API endpoint</strong></a>:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/summarize-blog&quot;, async (\n    string slug,\n    BlogService blogService,\n    BlogSummarizer blogSummarizer) =&gt;\n{\n    var content = await blogService.GetBlogContentAsync(slug);\n    var category = await blogSummarizer.SummarizeBlogAsync(content);\n\n    return Results.Ok(new\n    {\n        slug,\n        category,\n        content = $&quot;{content.Substring(0, 50)}...&quot;\n    });\n});\n</code></pre>\n<p>Call this endpoint with a blog post URL slug, and you get back the category and a preview of the content.\nThe dependency injection handles service resolution, and Aspire manages the AI model connection behind the scenes.</p>\n<p>Here's an example response:</p>\n<pre><code class=\"language-json\">{\n  &quot;slug&quot;: &quot;screaming-architecture&quot;, // https://www.milanjovanovic.tech/blog/screaming-architecture\n  &quot;category&quot;: &quot;Architecture&quot;,\n  &quot;content&quot;: &quot;If you were to glance at the folder structure of y...&quot;\n}\n</code></pre>\n<p>Here's the distributed trace in Aspire showing the request and response flow.\nYou can see the request to the blog service and the AI model.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_155/distributed_trace.png\" alt=\"Distributed trace in Aspire showing the request and response flow using GitHub Models\">\n<h2>Wrapping Up</h2>\n<p>The GitHub Models integration with .NET Aspire removes much of the complexity from adding AI to your applications.\nYou get:</p>\n<ul>\n<li>Simple configuration through the AppHost pattern</li>\n<li>Automatic service discovery and connection management</li>\n<li>Access to multiple AI models without vendor lock-in</li>\n<li>The same observability and deployment benefits as other Aspire resources</li>\n</ul>\n<p>Whether you're <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><strong>adding AI to an existing system</strong></a> or building something new, this integration provides the foundation you need.\nStart with simple categorization or summarization, then expand as you learn what works for your use case.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-generative-ai-applications-with-github-models-and-dotnet-aspire",
            "title": "Building Generative AI Applications With GitHub Models and .NET Aspire",
            "summary": "Discover how to integrate AI into your .NET applications in under an hour using GitHub Models and .NET Aspire 9.4.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_155.png",
            "date_modified": "2025-08-16T00:00:00.000Z",
            "date_published": "2025-08-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-5-most-common-rest-api-design-mistakes-and-how-to-avoid-them",
            "content_html": "<p>The five mistakes I see most often are inconsistent naming and structure, bumping the version instead of evolving the contract, no pagination or filtering, vague error responses, and security left for phase 2.\nEvery one of them is harder to fix once clients depend on the API.</p>\n<p>Bad APIs create friction for developers, increase maintenance costs, and make change risky.\nGood API design doesn't mean following every &quot;best practice&quot; blindly.\nIt means choosing the right trade-offs for your context and sticking to them.</p>\n<p>Here are 5 common mistakes I see repeatedly, why they cause problems, and how to avoid them with pragmatic, battle-tested solutions.</p>\n<h2>1. Inconsistent Naming and Structure</h2>\n<p>Naming is the first thing consumers see.\nInconsistency here leads to constant documentation lookups, broken expectations, and more bugs.</p>\n<p>Look, we've all been there.\nYou successfully call <code>/users</code> and <code>/products</code>, so naturally you try <code>/orders</code>.\nBut nope, this API uses <code>/order-list</code> for some reason.\nNow you're back to the docs, breaking your flow, wondering why anyone would do this.\nMultiply this friction by dozens of endpoints, and you've built an API that makes developers want to flip tables.</p>\n<p>I get it, deep URL hierarchies like <code>/users/{id}/habits/{habitId}/entries/{entryId}/comments/{commentId}</code> feel satisfying.\nThey mirror your beautiful domain model!\nBut here's the thing: you've just <strong>hardcoded your entire data structure</strong> into your URLs.\nWhen business requirements change (and they will), you can't reorganize without breaking clients.</p>\n<p>Plus, what happens when someone only has a comment ID?\nThey need to somehow figure out the user, habit, and entry IDs just to fetch one comment.\nThat's ridiculous.</p>\n<p>Keep it simple with <strong>plural nouns</strong>: <code>/users</code>, <code>/habits</code>, <code>/entries</code>.\nNo more guessing if it's <code>user</code> or <code>users</code>.</p>\n<p><strong>Only nest when something truly belongs to something else.</strong>\nUser settings belong to and die with the user, so <code>/users/{id}/settings</code> makes sense.\nComments can exist independently, so <code>/users/{id}/posts/{postId}/comments</code> can be simplified.</p>\n<p>Instead, flatten with filters:</p>\n<pre><code class=\"language-http\"># Instead of deep nesting\nGET /users/{userId}/habits/{habitId}/entries\n\n# Use filters for flexibility\nGET /entries?userId={userId}&amp;habitId={habitId}\nGET /entries?habitId={habitId}  # Now you can get entries without knowing the user\n</code></pre>\n<p>And please, for the love of all that is holy, wrap your arrays:</p>\n<pre><code class=\"language-json\">{\n  &quot;data&quot;: [\n    {\n      &quot;id&quot;: &quot;e_8YH&quot;,\n      &quot;habitId&quot;: &quot;code-review&quot;,\n      &quot;at&quot;: &quot;2025-08-08T09:17:34Z&quot;,\n      &quot;value&quot;: 5,\n      &quot;unit&quot;: &quot;reviews&quot;,\n      &quot;tags&quot;: [&quot;team&quot;]\n    },\n    {\n      &quot;id&quot;: &quot;e_8Z2&quot;,\n      &quot;habitId&quot;: &quot;deep-work&quot;,\n      &quot;at&quot;: &quot;2025-08-07T07:00:00Z&quot;,\n      &quot;value&quot;: 2,\n      &quot;unit&quot;: &quot;pomodoros&quot;,\n      &quot;note&quot;: &quot;EF filters optimized&quot;\n    }\n  ],\n  &quot;total&quot;: 42,\n  &quot;hasMore&quot;: true,\n  &quot;nextCursor&quot;: &quot;cursor_01J9KaBcd&quot;\n}\n</code></pre>\n<p>I know it feels like pointless boilerplate now, but trust me, when you need to add pagination info six months from now,\nyou'll thank yourself for not having to break every client that expects a raw array.</p>\n<p>Yeah, the <a href=\"https://en.wikipedia.org/wiki/REST\">REST</a> purists will complain that filters aren't &quot;RESTful enough.&quot;\nLet them.\nYour API will be flexible, maintainable, and actually pleasant to use.\nI'll take that over conceptual purity any day.</p>\n<h2>2. Poor Versioning Strategy</h2>\n<p>Everyone defaults to <a href=\"https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore\"><strong>versioning</strong></a> (<code>/v1/users</code>) thinking they're being smart about future changes.\nSpoiler: they're not. They're creating a maintenance nightmare.</p>\n<p>Here's what actually happens when you have v1, v2, and v3 running:</p>\n<ul>\n<li>Every bug needs to be fixed three times</li>\n<li>Every security patch needs three deployments</li>\n<li>Your docs become a choose-your-own-adventure novel</li>\n<li>Support has no idea which version that angry customer is using</li>\n<li>You spend weekends maintaining code you wrote two years ago</li>\n</ul>\n<p>But the worst part?\n<strong>Versioning makes you lazy</strong>.\nInstead of thinking &quot;how can I evolve this without breaking clients?&quot; you just think &quot;eh, I'll bump the version.&quot;\nNow your clients have to rewrite their entire integration because you wanted to rename a field.</p>\n<p>Watch this disaster unfold:</p>\n<pre><code>v1: GET /users returns {id, name, email}\nv2: GET /users returns {id, fullName, emailAddress}  // &quot;looks cleaner!&quot;\nv3: GET /users returns {id, firstName, lastName, email}  // &quot;we need split names!&quot;\n</code></pre>\n<p>Congrats, you're now maintaining three different response formats for the same damn data.\nThat v1 client?\nThey'll never get new features unless they rewrite everything.\nFound a critical bug? Hope you enjoy patching it three times!</p>\n<p>Here's the <strong>radical idea</strong>: <strong>don't version at all</strong>.\nI'm serious.</p>\n<p>Add fields, don't replace them:</p>\n<pre><code class=\"language-json\">// What you ship first\n{ &quot;id&quot;: 1, &quot;name&quot;: &quot;John Doe&quot; }\n\n// What you ship later (keeping the old field)\n{\n  &quot;id&quot;: 1,\n  &quot;name&quot;: &quot;John Doe&quot;,  // Still there! Mark it deprecated in docs\n  &quot;firstName&quot;: &quot;John&quot;,\n  &quot;lastName&quot;: &quot;Doe&quot;\n}\n</code></pre>\n<p>Need optional features?\nUse query parameters:</p>\n<pre><code class=\"language-http\">GET /users/{id}?include=habits,entries\nGET /users/{id}?format=detailed\n</code></pre>\n<p>If you absolutely must make a breaking change (and really think about this), create a new resource:</p>\n<pre><code class=\"language-http\"># Old faithful, unchanged\nGET /users/{id}\n\n# New hotness\nGET /userProfiles/{id}\n</code></pre>\n<p>When breaking changes are truly unavoidable, at least be a decent human about it.\nGive people 6-12 months notice.\nRun both versions in parallel.\nWrite a migration guide that doesn't suck.\nAnd for crying out loud, monitor who's still using the old stuff so you can reach out before pulling the plug.</p>\n<p>Yes, this means you need to actually think about your API design upfront.\nYou can't just YOLO field names and fix them later.\nBut that constraint will make you design better APIs, and future-you will buy present-you a beer.</p>\n<p>If you want to learn more about this, I recommend reading <a href=\"https://medium.com/good-api/api-change-management-2fe5bba32e9b\">API Change Management</a>.</p>\n<h2>3. Ignoring Pagination, Filtering, and Searching</h2>\n<p>That <code>GET /entries</code> endpoint works great with your 10 test records.\nThen you launch, get actual users, and suddenly you're returning 100,000 entries in a single response.\nYour API times out, your mobile users on crappy connections hate you, and your cloud bill makes you cry.</p>\n<p>&quot;We'll add pagination later,&quot; you said.\nWell, now it's later, and adding pagination means breaking every client that expects an array.\nNice job.</p>\n<p>Without filtering, your clients are downloading thousands of records to find the five they actually need.\nIt's like making someone download all of Wikipedia to read one article.\nYour servers are melting, serializing data nobody wants.\nYour users are burning through their data plans.\nEveryone loses.</p>\n<p><strong>Filtering</strong> is for when you know exactly what you want:</p>\n<pre><code class=\"language-http\">GET /entries?habitId=123&amp;date=2025-08-01&amp;status=completed\n</code></pre>\n<p><strong>Searching</strong> is for when you kinda know what you want:</p>\n<pre><code class=\"language-http\">GET /entries/search?q=morning+run+park\n</code></pre>\n<p>Don't try to be clever and combine them.\nFiltering uses your database indexes efficiently.\nSearching needs full-text magic.\nMix them and you'll end up with something that does neither well.</p>\n<p>For pagination, you've got two choices, and they both kinda suck in different ways.</p>\n<p><strong>Offset/limit</strong> is what everyone starts with:</p>\n<pre><code class=\"language-http\">GET /entries?offset=100&amp;limit=50\n             # or you can call them skip and take\n             # or you can call them page and pageSize\n             # use whatever you like best\n</code></pre>\n<p>It's dead simple and lets users jump to page 5, but here's the fun part: add or delete an item while someone's paginating, and they'll either skip entries or see duplicates.\nPlus, asking for offset=10000 makes your database cry as it counts through all those rows.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive\"><strong>Cursor-based pagination</strong></a> is the &quot;proper&quot; solution:</p>\n<pre><code class=\"language-http\">GET /entries?limit=50&amp;cursor=eyJpZCI6MTIzfQ==\n</code></pre>\n<p>Rock solid, no skipped items, consistent performance.\nBut you can't jump to arbitrary pages and cursors can become invalid if underlying data changes significantly.</p>\n<p>Yeah, implementing all this is a pain.\nYou need cursor encoding, parameter validation, query optimization.\nBut trying to add it after launch?\nWouldn't recommend.\nJust build it right the first time.</p>\n<h2>4. Unclear or Inconsistent Error Handling</h2>\n<p><code>{&quot;error&quot;: &quot;An error occurred&quot;}</code> — if you return this, I hate you.</p>\n<p>Seriously, when your API spits out these useless errors, here's what happens: I try random stuff hoping something works.\nI add defensive code everywhere because I don't trust you.\nI flood your support channel asking what's wrong.\nThen I complain about your API on social media (any publicity is good publicity, eh?).</p>\n<p>A good error tells me three things: <strong>what broke</strong>, <strong>why it broke</strong>, and <strong>how to fix it</strong>.\nIs that so hard?</p>\n<p>Stop inventing your own janky error format.\nUse <a href=\"https://milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis\"><strong>Problem Details</strong></a> (RFC 9457) like a civilized developer:</p>\n<pre><code class=\"language-json\">{\n  &quot;type&quot;: &quot;https://api.example.com/errors/validation-failed&quot;,\n  &quot;title&quot;: &quot;Validation Failed&quot;,\n  &quot;status&quot;: 400,\n  &quot;detail&quot;: &quot;The request body contains invalid fields&quot;,\n  &quot;instance&quot;: &quot;/habits/123&quot;,\n  &quot;errors&quot;: [\n    {\n      &quot;field&quot;: &quot;name&quot;,\n      &quot;reason&quot;: &quot;Must be between 1 and 100 characters&quot;,\n      &quot;value&quot;: &quot;&quot;\n    },\n    {\n      &quot;field&quot;: &quot;frequency&quot;,\n      &quot;reason&quot;: &quot;Must be one of: daily, weekly, monthly&quot;,\n      &quot;value&quot;: &quot;sometimes&quot;\n    }\n  ]\n}\n</code></pre>\n<p>See how that actually helps me fix the problem? Revolutionary, I know.</p>\n<p>And please use the right <a href=\"https://milanjovanovic.tech/blog/rest-api-http-status-codes\"><strong>status codes</strong></a>. It's not that hard:</p>\n<ul>\n<li><code>400 Bad Request</code>: You sent garbage</li>\n<li><code>401 Unauthorized</code>: Who are you?</li>\n<li><code>403 Forbidden</code>: I know who you are, but no</li>\n<li><code>404 Not Found</code>: That thing doesn't exist</li>\n<li><code>409 Conflict</code>: That conflicts with something</li>\n<li><code>422 Unprocessable Entity</code>: I understand what you want, but it's wrong<br>\n(<em>I don't use this personally, and prefer returning 400 for validation failures</em>)</li>\n<li><code>429 Too Many Requests</code>: Slow down, cowboy</li>\n<li><code>500 Internal Server Error</code>: We screwed up</li>\n<li><code>503 Service Unavailable</code>: We're drowning, try again later</li>\n</ul>\n<p>Now, don't go leaking your entire stack trace in production like an amateur.\nGive friendly errors to users, detailed errors in dev/staging, and log the gory details server-side where you can actually use them.</p>\n<h2>5. Ignoring Security Until It's Too Late</h2>\n<p>&quot;We'll add auth in phase 2&quot; — famous last words before your API becomes a data buffet for hackers.\nAsk the <a href=\"https://apnews.com/article/5433d5929bdfeb73f495d4775580a55f\">Tea app</a> how that worked out for them.</p>\n<p>Here's what happens when you try to bolt on security later: Every client breaks when you add authentication.\nThat data you've been leaking? It's probably been scraped already.\nYour compliance audit? Failed.\nThat one security incident? Your users will bring it up for years.</p>\n<p><strong>Authentication</strong> (who are you?) and <strong>Authorization</strong> (what can you do?) are different things.\nI've seen so many APIs that check if you're logged in but never check if you should actually access that data.\nDon't be that person.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/advanced-rate-limiting-use-cases-in-dotnet\"><strong>Rate limiting</strong></a> isn't just about stopping abuse, it's about fairness.\nStart simple: 1000 requests per hour per API key.\nWhen they hit the limit, return <code>429</code> with headers showing when they can try again.\nThen get fancy: different limits for different endpoints, higher limits for paying customers,\nlower limits for that one client who keeps doing weird stuff.</p>\n<p><strong>HTTPS everywhere</strong>.\nYes, even for your internal &quot;no one will ever find this&quot; API.\nIt's 2025, not 2005.\n<a href=\"https://letsencrypt.org/\">Let's Encrypt</a> is free.\nYou have no excuse.</p>\n<p>Look, security makes things slower and more complex.\nAuth checks on every request, encryption overhead, state management for rate limiting, it all adds up.\nBut you know what's worse?\nExplaining to your users why their data is being sold on the dark web.\nBuild security in from the start, or prepare for a world of pain.</p>\n<h2>Final Thoughts</h2>\n<p>Good API design isn't about perfection, it's about making intentional, informed decisions.\nEvery choice is a tradeoff.\nConsistency might limit flexibility.\nSecurity will impact performance.\nStability means slower innovation.</p>\n<p>Here's what actually matters: <strong>know your tradeoffs and own them</strong>.\nDocument why you made these choices (future you will thank you).\nStay consistent even when it's tempting not to.\nDesign for evolution, not some imaginary perfect future.\nAnd listen to your users, but don't turn your API into a frankenstein monster trying to please everyone.</p>\n<p><strong>Your API is a promise to other developers</strong>.\nEvery time you break that promise (with a breaking change, an inconsistent pattern, or a useless error message) you lose their trust.\nAnd trust me, developers hold grudges.</p>\n<p>Build the API you'd want to use.\nYour developers will thank you, your support team will thank you, and honestly, you'll thank yourself six months from now when you have to maintain this thing.</p>\n<p>Want to dive deeper? Check out my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a> course where I cover all of this (and more).</p>\n<p>P.S. Any sharp language here is just me being snarky for emphasis.\nI'm critiquing patterns, not people.\nDon't take the strong wording too seriously.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-5-most-common-rest-api-design-mistakes-and-how-to-avoid-them",
            "title": "The 5 Most Common REST API Design Mistakes (and How to Avoid Them)",
            "summary": "Five REST API design mistakes I see all the time, with practical fixes. Use consistent resource naming, evolve contracts instead of bumping versions, add…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_154.png",
            "date_modified": "2025-08-09T00:00:00.000Z",
            "date_published": "2025-08-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith",
            "content_html": "<p>Enforce data boundaries in the database, not only in code.\nGive each module its own PostgreSQL schema and login role, grant that role privileges on its schema alone, and point one EF Core <code>DbContext</code> per module at it with the module's connection string.\nCross-module reads go through a view or an API.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>Modular monoliths</strong></a> promise the productivity of a monolith and the clear boundaries of microservices.\nThey work because each module is self-contained: its domain model, behavior and data live behind a boundary.\nBut one of the hardest places to maintain those boundaries is in the database.</p>\n<p>Nothing stops a developer from running a rogue <code>JOIN</code> across tables or bypassing a public API.</p>\n<p>In previous articles, I described <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>four levels of data isolation</strong></a> (table, schema, database and alternative persistence)\nand argued that <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>modules should expose explicit APIs</strong></a> to access their data.</p>\n<p>This article goes deeper on the database side.\nYou'll learn how to carve out logical and physical boundaries using PostgreSQL schemas and roles and EF Core,\nwhy those choices matter, and how to handle cross-cutting queries without breaking encapsulation.</p>\n<h2>Why enforce database boundaries?</h2>\n<p>In a modular monolith each module <strong>owns its data</strong>.\nIf Module A reaches into Module B's tables, you lose this constraint and your modules become tightly coupled.\nInstead, B exposes a public API and hides its persistence logic from consumers.</p>\n<p>Beyond clean code, enforcing boundaries at the database level protects you against mistakes\nand makes it easier to extract a module into its own service later.</p>\n<p>Database schemas act like folders: they let you organise objects and share a database among many users,\nbut a user can access any schema only if they have privileges.\nThis means we can deliberately lock each module to its own schema.</p>\n<p><strong>The strategy</strong></p>\n<ul>\n<li>Create a <a href=\"https://milanjovanovic.tech/blog/schema-per-module-vs-database-per-module\"><strong>schema per module</strong></a> and a dedicated database role.</li>\n<li>Grant that role privileges only on its schema and set its default search path.</li>\n<li>Use EF with one <code>DbContext</code> per module, setting a default schema and connection string per module.</li>\n<li>For cross-cutting queries, publish a read-only database view that acts like a public API.</li>\n<li><em>Optional</em>: Use row-level security policies to restrict access within a table.</li>\n</ul>\n<p>These practices give us <strong>enforceable boundaries</strong> while keeping the <strong>operational overhead low</strong>.</p>\n<h2>Schemas, roles and search paths</h2>\n<p>PostgreSQL lets you create multiple schemas within a single database.\nA module can define its own schema and a role that owns it.\nThe role only has usage and table-level privileges on that schema.\nFor example, for an orders module:</p>\n<pre><code class=\"language-sql\">-- create a user for the module and its schema\nCREATE ROLE orders_role LOGIN PASSWORD 'orders_secret';\nCREATE SCHEMA orders AUTHORIZATION orders_role;\nGRANT USAGE ON SCHEMA orders TO orders_role;\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA orders TO orders_role;\nALTER ROLE orders_role SET search_path = orders;\n</code></pre>\n<p>The <code>ALTER ROLE</code> command sets the role's default search path so unqualified names resolve to the module's schema.\nIf you don't want to rely on the search path, always qualify table names (<code>orders.table_name</code>).</p>\n<p>Row-level security (RLS) allows you to filter rows based on a policy expression.\nYou enable RLS on a table and define a policy referencing <code>current_user</code> or columns:</p>\n<p>RLS is powerful for multi-tenant scenarios or sensitive data, but it adds complexity.\nStart with schemas and roles and add RLS only when necessary.</p>\n<h2>Configuring EF schemas and multiple DbContexts</h2>\n<p>I assume readers are comfortable with EF Core, so we'll skip the basics and focus on what matters for modular monoliths:</p>\n<ul>\n<li>Use <a href=\"https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application\"><strong>one DbContext per module</strong></a>.\nEach context contains only the entities of its module, and you call <code>modelBuilder.HasDefaultSchema(&quot;orders&quot;)</code> in <code>OnModelCreating</code>\nto map entities to the correct schema.\nSetting a default schema also affects sequences and migrations.</li>\n<li>Provide a connection string per module using the module's role.\nEven if modules share the same database, separate credentials ensure that a misconfigured context cannot access another schema.</li>\n<li>Configure the migrations history table in each context using <code>MigrationsHistoryTable(&quot;__EFMigrationsHistory&quot;, &quot;orders&quot;)</code>\nso that EF's migration metadata stays within the module's schema.</li>\n</ul>\n<p>These settings ensure EF Core queries and migrations respect the boundaries established by the database.</p>\n<h2>Step-by-step: Enforcing module boundaries</h2>\n<p>Suppose we have two modules (<strong>Orders</strong> and <strong>Shipping</strong>) and we want to enforce boundaries between them.\nHere's what we need to do:</p>\n<ol>\n<li>\n<p><strong>Create schemas and roles</strong>.\nUse SQL to create orders and shipping schemas and their corresponding roles (<code>orders_role</code>, <code>shipping_role</code>).\nGrant each role privileges only on its schema.</p>\n<pre><code class=\"language-sql\">-- Orders schema and role\nCREATE ROLE orders_role LOGIN PASSWORD 'orders_secret';\nCREATE SCHEMA orders AUTHORIZATION orders_role;\nGRANT USAGE ON SCHEMA orders TO orders_role;\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA orders TO orders_role;\nALTER ROLE orders_role SET search_path = orders;\n\n -- Shipping schema and role\n CREATE ROLE shipping_role LOGIN PASSWORD 'shipping_secret';\n CREATE SCHEMA shipping AUTHORIZATION shipping_role;\n GRANT USAGE ON SCHEMA shipping TO shipping_role;\n GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA shipping TO shipping_role;\n ALTER ROLE shipping_role SET search_path = shipping;\n</code></pre>\n</li>\n<li>\n<p><strong>Add connection strings</strong>. In configuration, define separate connection strings per module:</p>\n<pre><code class=\"language-json\">{\n  &quot;ConnectionStrings&quot;: {\n    &quot;Orders&quot;: &quot;Host=localhost;Database=appdb;Username=orders_role;Password=orders_secret&quot;,\n    &quot;Shipping&quot;: &quot;Host=localhost;Database=appdb;Username=shipping_role;Password=shipping_secret&quot;\n  }\n}\n</code></pre>\n</li>\n<li>\n<p><strong>Define DbContexts</strong>. Each module defines its own context and sets the default schema. For the Orders module:</p>\n<pre><code class=\"language-csharp\">public class OrdersDbContext : DbContext\n{\n    public DbSet&lt;Order&gt; Orders { get; set; } = default!;\n    public DbSet&lt;OrderLine&gt; OrderLines { get; set; } = default!;\n\n    public OrdersDbContext(DbContextOptions&lt;OrdersDbContext&gt; options) : base(options) { }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        // set default schema for all entities in this context\n        modelBuilder.HasDefaultSchema(&quot;orders&quot;);\n        // optional: configure tables explicitly\n        modelBuilder.Entity&lt;Order&gt;().ToTable(&quot;orders&quot;);\n        modelBuilder.Entity&lt;OrderLine&gt;().ToTable(&quot;order_lines&quot;);\n        base.OnModelCreating(modelBuilder);\n    }\n}\n</code></pre>\n<p>Register each context with its connection string and specify the migrations history table:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddDbContext&lt;OrdersDbContext&gt;(options =&gt;\n    options.UseNpgsql(builder.Configuration.GetConnectionString(&quot;Orders&quot;),\n        o =&gt; o.MigrationsHistoryTable(&quot;__EFMigrationsHistory&quot;, &quot;orders&quot;)));\n\nbuilder.Services.AddDbContext&lt;ShippingDbContext&gt;(options =&gt;\n    options.UseNpgsql(builder.Configuration.GetConnectionString(&quot;Shipping&quot;),\n        o =&gt; o.MigrationsHistoryTable(&quot;__EFMigrationsHistory&quot;, &quot;shipping&quot;)));\n</code></pre>\n</li>\n<li>\n<p><strong>Maintain migrations separately</strong>.\nBecause each module has its own context and schema, you maintain migrations separately.\nWhen generating a migration, specify the context:</p>\n<pre><code class=\"language-bash\">dotnet ef migrations add InitialOrders --context OrdersDbContext --output-dir Data/Migrations/Orders\n</code></pre>\n<p>Repeat this for the Shipping context.\nEF Core will generate migration classes that create tables within the specified schema (because of <code>HasDefaultSchema</code>).\nRemember to apply the migrations in the correct order when deploying. You can automate this by having a migration runner iterate through the contexts.</p>\n</li>\n</ol>\n<p>With schemas, roles and multiple contexts in place, the data boundary becomes enforceable at the database level:</p>\n<ul>\n<li>The <strong>Orders</strong> module's <code>DbContext</code> knows only about the orders schema and uses credentials that have no privileges on the <strong>Shipping</strong> schema.</li>\n<li>The <strong>Shipping</strong> module cannot query <code>orders.orders</code> directly because its role lacks the necessary privileges.</li>\n<li>Cross-module communication must go through the module's public API (or an asynchronous event).\nThis explicit coupling makes dependencies obvious and maintainable.</li>\n</ul>\n<h2>Cross-cutting queries</h2>\n<p>Even in a modular system you occasionally need a screen that spans multiple modules,\nsuch as an <strong>Order History</strong> page that shows order and shipping data.\nResist the temptation to <code>JOIN</code> across schemas.\nTwo approaches can help:</p>\n<ul>\n<li>\n<p><strong>Dedicated read model</strong>.\nOne module owns a view model and <a href=\"https://milanjovanovic.tech/blog/event-driven-communication-modules\"><strong>subscribes to events</strong></a> from others.\nThis pattern works well when modules might be extracted later.</p>\n</li>\n<li>\n<p><strong>Database views with privileges</strong>.\nSince our modules share a database, we can create a read-only view in the public schema that joins the relevant tables.\nWe grant <code>SELECT</code> on the view to a special role or module.\nThis view acts like a controlled public API.\nConsumers query the view but cannot access the underlying tables directly.\nThe trade-off is similar to calling a synchronous API: if you later split the database, the view will have to be replaced with a service call.</p>\n</li>\n</ul>\n<p>For example:</p>\n<pre><code class=\"language-sql\">CREATE VIEW public.order_summary AS\nSELECT o.id, o.total, s.status\nFROM orders.orders o\nJOIN shipping.shipments s ON s.order_id = o.id;\n\n-- grant read access to a reporting role\nGRANT SELECT ON public.order_summary TO reporting_role;\n</code></pre>\n<p>This approach lets you build dashboards or admin screens without breaking boundaries.</p>\n<h2>Conclusion</h2>\n<p>By giving each module its own schema, role and DbContext, and by controlling cross-module access via views or APIs,\nyou <strong>ensure that boundaries in your modular monolith are enforceable</strong> rather than aspirational.\nThis discipline makes it easier to evolve your system and paves the way for a future <a href=\"https://milanjovanovic.tech/blog/when-to-extract-module-to-microservice\"><strong>microservice extraction</strong></a>.</p>\n<p>If you found this article valuable, check out my previous posts on modular monoliths:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\">What Is a Modular Monolith?</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\">Modular Monolith Data Isolation</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\">Internal vs Public APIs in Modular Monoliths</a></li>\n</ul>\n<p>If you want a structured, hands-on approach to building modular systems, from defining boundaries to extracting services,\ncheck out my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course.\nJoin more than 2,100+ students who have mastered modular monoliths with it.\nThe course walks through these patterns in depth and shows how to apply them in a real codebase.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith",
            "title": "How to Keep Your Data Boundaries Intact in a Modular Monolith",
            "summary": "Want real boundaries in your modular monolith? This article shows how to enforce them at the database level using Postgres schemas, roles, and EF Core — so…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_153.png",
            "date_modified": "2025-08-02T00:00:00.000Z",
            "date_published": "2025-08-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/named-query-filters-in-ef-10-multiple-query-filters-per-entity",
            "content_html": "<p><strong>Named query filters</strong> are a feature EF 10 introduced: you attach multiple global query filters to one entity by passing a name to <code>HasQueryFilter</code>.\nYou can then disable individual filters by name with <code>IgnoreQueryFilters</code> instead of turning them all off at once.\nThat makes combinations like soft deletion plus multi-tenancy much safer.</p>\n<p>Entity Framework Core's global query filters have long been a convenient way to apply common conditions to all queries on an entity.\nThey're especially handy in scenarios like <a href=\"https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core\"><strong>soft deletion</strong></a>\nand <a href=\"https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core\"><strong>multi-tenancy</strong></a>,\nwhere you want the same <code>WHERE</code> clause added automatically to every query.</p>\n<p>Previous versions of EF Core, however, suffered from <strong>one big limitation</strong>: each entity type could only have one filter defined.\nIf you needed to combine multiple conditions (for example, soft-delete and tenant isolation)\nyou either had to write explicit <code>&amp;&amp;</code> expressions or manually disable and reapply filters in specific queries.</p>\n<p>With EF 10, that changes.\nThe new <strong>named query filters</strong> feature lets you attach multiple filters to a single entity and reference them by name.\nYou can then disable individual filters as needed, rather than turning off all filters at once.</p>\n<p>Let's explore this new capability, why it matters, and some practical ways to use it.</p>\n<h2>What Are Query Filters?</h2>\n<p>If you've used EF Core for a while, you may already be familiar with\n<a href=\"https://learn.microsoft.com/en-us/ef/core/querying/filters\">global query filters</a>.\nA query filter is a condition that EF automatically applies to all queries for a particular entity type.\nUnder the hood, EF adds a <code>WHERE</code> clause whenever that entity is queried. Typical uses include:</p>\n<ul>\n<li><strong>Soft deletion</strong>: filtering out rows where IsDeleted is true so that deleted records don't show up in queries by default</li>\n<li><strong>Multi-tenancy</strong>: filtering by a TenantId so that each tenant only sees its own data</li>\n</ul>\n<p>For example, a soft-delete filter might be configured like this:</p>\n<pre><code class=\"language-csharp\">modelBuilder.Entity&lt;Order&gt;()\n    .HasQueryFilter(order =&gt; !order.IsDeleted);\n</code></pre>\n<p>With the filter in place, every query on <code>Orders</code> automatically excludes soft-deleted records.\nTo include deleted data (say, for an admin report), you can call <code>IgnoreQueryFilters()</code> on the query.\nThe downside is that all filters on that entity are disabled,\nwhich opens the door to accidentally leaking data you don't intend to show.</p>\n<h2>Using Multiple Query Filters</h2>\n<p>Until now, EF permitted only one query filter per entity.\nIf you called <code>HasQueryFilter</code> twice on the same entity, the second call overwrote the first.\nTo combine filters you had to write a single expression with <code>&amp;&amp;</code>:</p>\n<pre><code class=\"language-csharp\">modelBuilder.Entity&lt;Order&gt;()\n    .HasQueryFilter(order =&gt; !order.IsDeleted &amp;&amp; order.TenantId == tenantId);\n</code></pre>\n<p>This works but makes it impossible to selectively disable one condition.\n<code>IgnoreQueryFilters()</code> disables both, forcing you to manually re-apply whichever filter you still need.\nEF 10 introduces a better alternative: <strong>named query filters</strong>.</p>\n<p>To attach multiple filters to an entity, call <code>HasQueryFilter</code> with a name for each filter:</p>\n<pre><code class=\"language-csharp\">modelBuilder.Entity&lt;Order&gt;()\n    .HasQueryFilter(&quot;SoftDeletionFilter&quot;, order =&gt; !order.IsDeleted)\n    .HasQueryFilter(&quot;TenantFilter&quot;, order =&gt; order.TenantId == tenantId);\n</code></pre>\n<p>Under the hood, EF creates separate filters identified by the names you provide.\nYou can now turn off just the soft-delete filter while keeping the tenant filter in place:</p>\n<pre><code class=\"language-csharp\">// Returns all orders (including soft‑deleted) for the current tenant\nvar allOrders = await context.Orders.IgnoreQueryFilters([&quot;SoftDeletionFilter&quot;]).ToListAsync();\n</code></pre>\n<p>If you omit the parameter array, <code>IgnoreQueryFilters()</code> disables all filters for the entity.</p>\n<h2>Tip: Using Constants for Filter Names</h2>\n<p>Named filters use string keys.\nHard-coding those names throughout your codebase makes it easy to introduce typos and brittle magic strings.\nTo avoid this, define constants or enums for your filter names and reuse them wherever needed.\nFor example:</p>\n<pre><code class=\"language-csharp\">public static class OrderFilters\n{\n    public const string SoftDelete = nameof(SoftDelete);\n    public const string Tenant = nameof(Tenant);\n}\n\nmodelBuilder.Entity&lt;Order&gt;()\n    .HasQueryFilter(OrderFilters.SoftDelete, order =&gt; !order.IsDeleted)\n    .HasQueryFilter(OrderFilters.Tenant, order =&gt; order.TenantId == tenantId);\n\n// Later in your query\nvar allOrders = await context.Orders.IgnoreQueryFilters([OrderFilters.SoftDelete]).ToListAsync();\n</code></pre>\n<p>Having the filter names defined in a single place reduces duplication and improves maintainability.\nAnother best practice is to wrap the ignore call in an extension method or repository\nso that consumers don't directly interact with filter names at all. For example:</p>\n<pre><code class=\"language-csharp\">public static IQueryable&lt;Order&gt; IncludeSoftDeleted(this IQueryable&lt;Order&gt; query)\n    =&gt; query.IgnoreQueryFilters([OrderFilters.SoftDelete]);\n</code></pre>\n<p>This makes your intent explicit and centralizes the filter logic in one place.</p>\n<h2>Wrapping Up</h2>\n<p>The introduction of <strong>named query filters</strong> in EF 10 removes one of the longstanding limitations\nof EF's <a href=\"https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core\"><strong>global query filters</strong></a> feature.\nYou can now:</p>\n<ul>\n<li>Attach multiple filters to a single entity and manage them individually</li>\n<li>Selectively disable specific filters in a LINQ query using <code>IgnoreQueryFilters([&quot;FilterName&quot;])</code></li>\n<li>Simplify common patterns like <a href=\"https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core\"><strong>soft deletion</strong></a> plus\n<a href=\"https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core\"><strong>multi-tenancy</strong></a> without resorting to complicated conditional logic</li>\n</ul>\n<p>Named query filters can become a powerful tool to keep your queries clean and your domain logic encapsulated.</p>\n<p>Whether you're building SaaS applications that isolate tenant data or\nensuring that deleted records stay hidden until you explicitly need them,\nEF 10's named query filters offer the flexibility you've been waiting for.</p>\n<p>Give them a try in the preview and start thinking about how they can simplify your codebase.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/named-query-filters-in-ef-10-multiple-query-filters-per-entity",
            "title": "Named Query Filters in EF 10 (multiple query filters per entity)",
            "summary": "EF 10 introduces named query filters, letting you attach multiple filters to a single entity and disable specific ones without turning off all query filters.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_152.png",
            "date_modified": "2025-07-26T00:00:00.000Z",
            "date_published": "2025-07-26T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp",
            "content_html": "<p>You can generate PDF reports in .NET for free by rendering an HTML template with Handlebars.NET and printing it with PuppeteerSharp, which drives headless Chromium.\nYou keep full CSS control over the layout.\nThe cost is a bundled browser: about 12 seconds on a cold start, around 580ms once it is warm.</p>\n<p>Sooner or later, every .NET developer needs to generate PDF reports.\nAnd generating polished PDF reports in .NET doesn't have to be painful.\nLet me explain how.</p>\n<p>My go-to method?</p>\n<p><strong>HTML to PDF conversion</strong>.\nIt's:</p>\n<ul>\n<li>Simple to implement</li>\n<li>Very flexible</li>\n<li>Ideal for stylized reports</li>\n</ul>\n<p>But many popular libraries require a commercial license.\nThis post walks you through a completely free approach using:</p>\n<ul>\n<li><strong>Handlebars.NET</strong> for templating</li>\n<li><strong>PuppeteerSharp</strong> for headless rendering</li>\n</ul>\n<p>This gives you full control over layout, styling, and content.\nIt's perfect for invoices, dashboards, and exports.</p>\n<p>We'll start from scratch and build up to a complex, dynamic invoice report with full styling, images, and even headers and footers.</p>\n<h2>Why HTML + Headless Browser?</h2>\n<p><strong>Pros:</strong></p>\n<ul>\n<li>Rich styling with CSS</li>\n<li>Easy to preview/debug in browser</li>\n<li>Supports charts/images via JS/CSS</li>\n<li>Full control over layout (media queries, page breaks, etc.)</li>\n</ul>\n<p><strong>Cons:</strong></p>\n<ul>\n<li>Requires bundling a browser (e.g. Chromium)</li>\n<li>Slower than native PDF libraries</li>\n<li>Slightly more setup complexity</li>\n</ul>\n<h2>Setting Up the Project</h2>\n<p>We'll start by installing the NuGet packages we need for Handlebars and PuppeteerSharp:</p>\n<pre><code class=\"language-powershell\">Install-Package Handlebars.Net\nInstall-Package PuppeteerSharp\n</code></pre>\n<p>Next, we'll create our first template.\nIt's a simple HTML document with some Handlebars placeholders.\nYou'll notice them with the <code>{{variable}}</code> syntax.\nThese placeholders will be replaced with actual data when rendering the template.</p>\n<pre><code class=\"language-html\">&lt;!-- Templates/InvoiceTemplate.html --&gt;\n&lt;!-- The file extension doesn't really matter. --&gt;\n&lt;html lang=&quot;en&quot;&gt;\n  &lt;head&gt;\n    &lt;style&gt;\n      body {\n        font-family: Arial;\n      }\n    &lt;/style&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;Invoice #{{Number}}&lt;/h1&gt;\n\n    &lt;p&gt;Date: {{formatDate IssuedDate}}&lt;/p&gt;\n\n    &lt;h2&gt;From:&lt;/h2&gt;\n    &lt;p&gt;{{SellerAddress.CompanyName}}&lt;/p&gt;\n    &lt;p&gt;{{SellerAddress.Email}}&lt;/p&gt;\n\n    &lt;h2&gt;To:&lt;/h2&gt;\n    &lt;p&gt;{{CustomerAddress.CompanyName}}&lt;/p&gt;\n    &lt;p&gt;{{CustomerAddress.Email}}&lt;/p&gt;\n\n    &lt;h2&gt;Items:&lt;/h2&gt;\n    &lt;table&gt;\n      &lt;tr&gt;\n        &lt;th&gt;Name&lt;/th&gt;\n        &lt;th&gt;Price&lt;/th&gt;\n      &lt;/tr&gt;\n      {{#each LineItems}}\n      &lt;tr&gt;\n        &lt;td&gt;{{Name}}&lt;/td&gt;\n        &lt;td&gt;{{formatCurrency Price}}&lt;/td&gt;\n      &lt;/tr&gt;\n      {{/each}}\n    &lt;/table&gt;\n\n    &lt;p&gt;&lt;strong&gt;Total: {{formatCurrency Total}}&lt;/strong&gt;&lt;/p&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n<p>The function calls, like <code>{{formatDate IssuedDate}}</code>, are custom helpers we can define in Handlebars.\nYou register them like this:</p>\n<pre><code class=\"language-csharp\">Handlebars.RegisterHelper(&quot;formatDate&quot;, (context, arguments) =&gt;\n{\n    if (arguments[0] is DateOnly date)\n    {\n        return date.ToString(&quot;dd/MM/yyyy&quot;);\n    }\n    return arguments[0]?.ToString() ?? &quot;&quot;;\n});\n</code></pre>\n<p>This allows us to format dates, currencies, or any other data type as needed.\nYou register these helpers before compiling the template, once per application start is enough.</p>\n<h2>Rendering the Template and PDF</h2>\n<p>How do we render this template and convert it to PDF?\nWe'll use <a href=\"https://github.com/Handlebars-Net/Handlebars.Net\">Handlebars.NET</a> to compile the template with data, then PuppeteerSharp to render it to PDF.</p>\n<p>First, we read the template file and compile it with <code>Handlebars</code>:</p>\n<pre><code class=\"language-csharp\">var template = File.ReadAllText(&quot;Templates/InvoiceTemplate.html&quot;);\nvar data = new {\n    customer = &quot;Milan Jovanović&quot;,\n    items = new[] {\n        new { description = &quot;Software License&quot;, price = 99 },\n        new { description = &quot;Support Plan&quot;, price = 49 }\n    }\n};\n\nvar compiledTemplate = Handlebars.Compile(template);\n\nstring html = compiledTemplate(data);\n</code></pre>\n<p>This gives us the final HTML with all placeholders replaced by actual data.</p>\n<p><strong>Note</strong>: You don't necessarily need to use Handlebars.\nYou can use any templating engine that suits your needs,\nlike <a href=\"https://milanjovanovic.tech/blog/flexible-pdf-reporting-in-net-using-razor-views\"><strong>Razor</strong></a> or <a href=\"https://github.com/scriban/scriban\">Scriban</a>.</p>\n<p>Next, we need to convert this HTML to PDF using PuppeteerSharp.\nWe'll launch a headless browser, set the content to our HTML, and then generate the PDF.\nHere's how we do it:</p>\n<pre><code class=\"language-csharp\">// Ensure PuppeteerSharp has the browser binaries\nvar browserFetcher = new BrowserFetcher();\nawait browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision);\n\n// Launch the browser and create a new page\nusing var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });\nusing var page = await browser.NewPageAsync();\n\n// Set the content of the page to our compiled HTML\nawait page.SetContentAsync(html);\n\n// Optional: wait for fonts to load if using custom fonts\nawait page.EvaluateExpressionHandleAsync(&quot;document.fonts.ready&quot;);\n\nbyte[] pdf = await page.PdfDataAsync(new PdfOptions {\n    Format = PaperFormat.A4,\n    PrintBackground = true,\n    MarginOptions = new MarginOptions\n    {\n        Top = &quot;50px&quot;,\n        Right = &quot;20px&quot;,\n        Bottom = &quot;50px&quot;,\n        Left = &quot;20px&quot;\n    }\n});\n</code></pre>\n<p>This gives us a byte array containing the PDF data.\nYou can then save it to a file or return it from an API endpoint.</p>\n<p>Here's a simple example of returning it from a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint:</p>\n<pre><code class=\"language-csharp\">return Results.File(pdf, &quot;application/pdf&quot;, &quot;invoice.pdf&quot;);\n</code></pre>\n<p>Here's what the document looks like when rendered:</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_151/simple_template.png\" alt=\"Simple PDF Template example rendered as PDF\">\n  </div>\n  <figcaption>\n    This is a simple PDF template example with dynamic templated content.\n  </figcaption>\n</figure>\n<h2>Enhancements: Images, Header/Footer, Styling</h2>\n<p>What are some improvements we can make to this basic setup?</p>\n<p>For example, you can add images to your template using the <code>&lt;img&gt;</code> tag.\nA simple approach is to use a base64-encoded image directly in the HTML.\nNote that we're passing the image data using the <code>LogoBase64</code> variable.</p>\n<pre><code class=\"language-html\">&lt;img\n  src=&quot;data:image/png;base64,{{LogoBase64}}&quot;\n  alt=&quot;Logo&quot;\n  style=&quot;height:50px; max-width:200px; object-fit:contain;&quot;\n/&gt;\n</code></pre>\n<p>We can also render dynamic headers and footers using PuppeteerSharp's built-in support.\nYou can define these in the <code>PdfOptions</code> object when generating the PDF.\nHere's an example:</p>\n<pre><code class=\"language-csharp\">var pdfOptions = new PdfOptions\n{\n    HeaderTemplate =\n        @&quot;&quot;&quot;\n        &lt;div style='font-size: 14px; text-align: center; padding: 10px;'&gt;\n            &lt;span style='margin-right: 20px;'&gt;&lt;span class='title'&gt;&lt;/span&gt;&lt;/span&gt;\n            &lt;span&gt;&lt;span class='date'&gt;&lt;/span&gt;&lt;/span&gt;\n        &lt;/div&gt;\n        &quot;&quot;&quot;,\n    FooterTemplate =\n        @&quot;&quot;&quot;\n        &lt;div style='font-size: 14px; text-align: center; padding: 10px;'&gt;\n            &lt;span style='margin-right: 20px;'&gt;Generated on &lt;span class='date'&gt;&lt;/span&gt;&lt;/span&gt;\n            &lt;span&gt;Page &lt;span class='pageNumber'&gt;&lt;/span&gt; of &lt;span class='totalPages'&gt;&lt;/span&gt;&lt;/span&gt;\n        &lt;/div&gt;\n        &quot;&quot;&quot;,\n    DisplayHeaderFooter = true\n};\n</code></pre>\n<p>PuppeteerSharp uses CSS classes like <code>title</code>, <code>date</code>, <code>pageNumber</code>, and <code>totalPages</code> to inject dynamic values.\nThis could be different for some other libraries, so check the documentation.</p>\n<p>Lastly, I want to mention that you can use CSS for advanced styling.\nThis can be inline in the HTML or in a separate CSS file.\nYou can also reference external stylesheets if needed, using the <code>&lt;link&gt;</code> tag.</p>\n<p>Here's a complete example of a more complex template with images, headers, and footers:</p>\n<pre><code class=\"language-html\">&lt;html lang=&quot;en&quot;&gt;\n  &lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot; /&gt;\n    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;\n    &lt;title&gt;Invoice #{{Number}}&lt;/title&gt;\n    &lt;style&gt;\n      /* Omitted for brevity */\n    &lt;/style&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;div class=&quot;invoice-container&quot;&gt;\n      &lt;!-- Header with Logo --&gt;\n      &lt;div\n        style=&quot;display: flex; justify-content: space-between; align-items: flex-start;&quot;\n      &gt;\n        &lt;div&gt;\n          &lt;h1 class=&quot;invoice-title&quot;&gt;Invoice #{{Number}}&lt;/h1&gt;\n          &lt;div class=&quot;invoice-dates&quot;&gt;\n            &lt;p&gt;&lt;strong&gt;Issued:&lt;/strong&gt; {{formatDate IssuedDate}}&lt;/p&gt;\n            &lt;p&gt;&lt;strong&gt;Due:&lt;/strong&gt; {{formatDate DueDate}}&lt;/p&gt;\n          &lt;/div&gt;\n        &lt;/div&gt;\n        &lt;div&gt;\n          {{#if LogoBase64}}\n          &lt;img\n            src=&quot;data:image/png;base64,{{LogoBase64}}&quot;\n            alt=&quot;Logo&quot;\n            style=&quot;height:50px; max-width:200px; object-fit:contain;&quot;\n          /&gt;\n          {{/if}}\n        &lt;/div&gt;\n      &lt;/div&gt;\n      &lt;hr\n        style=&quot;margin: 20px 0; border: none; border-top: 1px solid #e9ecef;&quot;\n      /&gt;\n\n      &lt;!-- Addresses - Side by Side --&gt;\n      &lt;div class=&quot;addresses&quot;&gt;\n        &lt;!-- Seller Address --&gt;\n        &lt;div class=&quot;address-box&quot;&gt;\n          &lt;h3 class=&quot;address-title&quot;&gt;From:&lt;/h3&gt;\n          &lt;div class=&quot;address-content&quot;&gt;\n            &lt;p class=&quot;company-name&quot;&gt;{{SellerAddress.CompanyName}}&lt;/p&gt;\n            &lt;p&gt;{{SellerAddress.Street}}&lt;/p&gt;\n            &lt;p&gt;{{SellerAddress.City}}, {{SellerAddress.State}}&lt;/p&gt;\n            &lt;p class=&quot;email&quot;&gt;{{SellerAddress.Email}}&lt;/p&gt;\n          &lt;/div&gt;\n        &lt;/div&gt;\n\n        &lt;!-- Customer Address --&gt;\n        &lt;div class=&quot;address-box&quot;&gt;\n          &lt;h3 class=&quot;address-title&quot;&gt;Bill To:&lt;/h3&gt;\n          &lt;div class=&quot;address-content&quot;&gt;\n            &lt;p class=&quot;company-name&quot;&gt;{{CustomerAddress.CompanyName}}&lt;/p&gt;\n            &lt;p&gt;{{CustomerAddress.Street}}&lt;/p&gt;\n            &lt;p&gt;{{CustomerAddress.City}}, {{CustomerAddress.State}}&lt;/p&gt;\n            &lt;p class=&quot;email&quot;&gt;{{CustomerAddress.Email}}&lt;/p&gt;\n          &lt;/div&gt;\n        &lt;/div&gt;\n      &lt;/div&gt;\n\n      &lt;!-- Items Table --&gt;\n      &lt;div class=&quot;items-section&quot;&gt;\n        &lt;h2 class=&quot;items-title&quot;&gt;Items&lt;/h2&gt;\n        &lt;table class=&quot;items-table&quot;&gt;\n          &lt;thead&gt;\n            &lt;tr&gt;\n              &lt;th&gt;#&lt;/th&gt;\n              &lt;th&gt;Description&lt;/th&gt;\n              &lt;th&gt;Price&lt;/th&gt;\n              &lt;th&gt;Qty&lt;/th&gt;\n              &lt;th&gt;Total&lt;/th&gt;\n            &lt;/tr&gt;\n          &lt;/thead&gt;\n          &lt;tbody&gt;\n            {{#each LineItems}}\n            &lt;tr&gt;\n              &lt;td&gt;{{@index}}&lt;/td&gt;\n              &lt;td&gt;{{Name}}&lt;/td&gt;\n              &lt;td&gt;{{formatCurrency Price}}&lt;/td&gt;\n              &lt;td&gt;{{formatNumber Quantity}}&lt;/td&gt;\n              &lt;td&gt;{{formatCurrency (multiply Price Quantity)}}&lt;/td&gt;\n            &lt;/tr&gt;\n            {{/each}}\n          &lt;/tbody&gt;\n        &lt;/table&gt;\n      &lt;/div&gt;\n\n      &lt;!-- Totals --&gt;\n      &lt;div class=&quot;totals&quot;&gt;\n        &lt;div class=&quot;totals-container&quot;&gt;\n          &lt;div class=&quot;totals-row subtotal&quot;&gt;\n            &lt;span&gt;Subtotal:&lt;/span&gt;\n            &lt;span&gt;{{formatCurrency Subtotal}}&lt;/span&gt;\n          &lt;/div&gt;\n          &lt;div class=&quot;totals-row&quot;&gt;\n            &lt;span&gt;Tax:&lt;/span&gt;\n            &lt;span&gt;{{formatCurrency 0}}&lt;/span&gt;\n          &lt;/div&gt;\n          &lt;div class=&quot;totals-row total&quot;&gt;\n            &lt;span&gt;Total:&lt;/span&gt;\n            &lt;span&gt;{{formatCurrency Total}}&lt;/span&gt;\n          &lt;/div&gt;\n        &lt;/div&gt;\n      &lt;/div&gt;\n    &lt;/div&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n<p>And the rendered PDF looks like this:</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_151/complex_template.png\" alt=\"Complex PDF Template example rendered as PDF with images, headers, and footers\">\n  </div>\n  <figcaption>\n    This is a complex PDF template example with CSS stylization, tables, images,\n    headers, and footers.\n  </figcaption>\n</figure>\n<h2>Downloading Binaries at Application Start</h2>\n<p>Here's a small tip: PuppeteerSharp requires the Chromium browser binaries to be downloaded at runtime.\nYou can do this by calling <code>BrowserFetcher.DownloadAsync()</code> before launching the browser.\nThis ensures the required browser version is available when you run your application.</p>\n<p>A simple way to do this is to add it to your application startup code or a background service:</p>\n<pre><code class=\"language-csharp\">public class BrowserSetupService : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        var browserFetcher = new BrowserFetcher();\n        await browserFetcher.DownloadAsync();\n    }\n}\n</code></pre>\n<p>Then register this service in your <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHostedService&lt;BrowserSetupService&gt;();\n</code></pre>\n<h2>Performance Considerations</h2>\n<p>Let's talk about performance.\nHow fast is this approach and can you use it at scale?</p>\n<p>The first thing to consider is the use a headless browser like Chromium.\nThis can be slower than native PDF libraries, especially for large documents or high concurrency.\nIt also adds <strong>overhead at runtime</strong> since the browser binaries need to be <strong>downloaded</strong> and <strong>launched</strong>.</p>\n<p>You should definitely consider moving this out of your main application.\nYou can use a <a href=\"https://milanjovanovic.tech/blog/background-jobs-clean-architecture\"><strong>background service</strong></a> or a separate microservice to handle PDF generation.\nEvent a cloud function can be a good fit if you need to scale.</p>\n<p>But as with anything performance-related, it depends on your specific use case.\nSo measure and profile your application to see if this approach meets your needs.</p>\n<p>Here are some benchmarks I ran on a sample invoice template.\nIt's nothing scientific, but it gives you an idea of the performance.\nI didn't test this with concurrent requests, but rather just the time it takes to generate a single PDF.</p>\n<p><strong>Cold Start</strong>: ~12s spent downloading + launching Chromium</p>\n<p><strong>Warm Run</strong>: ~580ms</p>\n<ul>\n<li>Template + HTML generation: ~13ms</li>\n<li>Browser reuse + rendering: ~550ms</li>\n</ul>\n<h2>Summary</h2>\n<p>HTML + PuppeteerSharp is one of the most pragmatic approaches for PDF reporting in .NET.</p>\n<p>It lets you:</p>\n<ul>\n<li>Design pixel-perfect layouts using familiar web technologies</li>\n<li>Inject dynamic data cleanly with Handlebars.NET</li>\n<li>Output high-quality PDFs with full styling, tables, and images</li>\n</ul>\n<p>And all of this without relying on commercial libraries.</p>\n<p>I've also written about <a href=\"https://milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore\"><strong>PDF generation</strong></a> in the past,\nwith libraries like <a href=\"https://www.questpdf.com/\">QuestPdf</a> or <a href=\"https://ironpdf.com/\">IronPdf</a>.\nYou can take a look at those if you want to compare approaches.</p>\n<p>The cold start can be expensive, but once warmed up, rendering is fast and reliable.\nYou get total layout control, CSS styling, and even dynamic headers and footers with page numbers and timestamps.</p>\n<p>If you're building internal dashboards, invoice generators, or export endpoints, this approach delivers excellent value.</p>\n<p>If you need pixel-perfect PDF reports in .NET and want full design control,\ncombining Handlebars.NET with PuppeteerSharp is a powerful approach.</p>\n<p>You'll trade some performance and setup cost for flexibility, but for most internal tools, dashboards, or customer-facing reports, it's worth it.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp",
            "title": "PDF Reporting in .NET With HTML Templates and PuppeteerSharp (and it's free)",
            "summary": "Generate PDF reports in .NET using HTML templates and a headless browser. We'll explore Handlebars.NET and PuppeteerSharp, compare alternatives, and analyze…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_151.png",
            "date_modified": "2025-07-19T00:00:00.000Z",
            "date_published": "2025-07-19T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-from-middleware-to-modern-handlers",
            "content_html": "<p>ASP.NET Core gives you three options for global error handling: custom middleware with a try-catch, the same middleware writing through <code>IProblemDetailsService</code>, and <code>IExceptionHandler</code>.\nFor new projects I reach for <code>IExceptionHandler</code> with <code>AddProblemDetails</code>, because each handler covers one exception type and the framework tries them in registration order.</p>\n<p>Let's talk about something we all deal with but often put off until the last minute - error handling in our ASP.NET Core apps.</p>\n<p>When something breaks in production, the last thing you want is a cryptic 500 error with zero context.\nProper error handling isn't just about logging exceptions.\nIt's about making sure your app fails gracefully and gives useful info to the caller (and you).</p>\n<p>In this article, I'll walk through the main options for global error handling in ASP.NET Core.</p>\n<p>We'll look at how I used to do it, what ASP.NET Core 9 offers now, and where each approach makes sense.</p>\n<h2>Middleware-Based Error Handling</h2>\n<p>The classic way to catch unhandled exceptions is with custom <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\"><strong>middleware</strong></a>.\nThis is where most of us start, and honestly, it still works great for most scenarios.</p>\n<pre><code class=\"language-csharp\">internal sealed class GlobalExceptionHandlerMiddleware(\n    RequestDelegate next,\n    ILogger&lt;GlobalExceptionHandlerMiddleware&gt; logger)\n{\n    public async Task InvokeAsync(HttpContext context)\n    {\n        try\n        {\n            await next(context);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, &quot;Unhandled exception occurred&quot;);\n\n            // Make sure to set the status code before writing to the response body\n            context.Response.StatusCode = ex switch\n            {\n                ApplicationException =&gt; StatusCodes.Status400BadRequest,\n                _ =&gt; StatusCodes.Status500InternalServerError\n            };\n\n            await context.Response.WriteAsJsonAsync(\n                new ProblemDetails\n                {\n                    Type = ex.GetType().Name,\n                    Title = &quot;An error occured&quot;,\n                    Detail = ex.Message\n                });\n        }\n    }\n}\n</code></pre>\n<p>Don't forget to add the middleware to the request pipeline:</p>\n<pre><code class=\"language-csharp\">app.UseMiddleware&lt;GlobalExceptionHandlerMiddleware&gt;();\n</code></pre>\n<p>This approach is solid and works everywhere in your pipeline.\nThe beauty is its simplicity: wrap everything in a try-catch, log the error, and return a consistent response.</p>\n<p>But once you start adding specific rules for different exception types (e.g. <code>ValidationException</code>, <code>NotFoundException</code>), this becomes a mess.\nYou end up with long <code>if</code> / <code>else</code> chains or more abstractions to handle each exception type.</p>\n<p>Plus, you're manually crafting JSON responses, which means you're probably not following\n<a href=\"https://www.rfc-editor.org/rfc/rfc9457\">RFC 9457 (Problem Details)</a> standards.</p>\n<h2>Enter IProblemDetailsService</h2>\n<p>Microsoft recognized this pain point and gave us <code>IProblemDetailsService</code> to standardize error responses.\nInstead of manually serializing our own error objects, we can use the built-in Problem Details format.</p>\n<pre><code class=\"language-csharp\">internal sealed class GlobalExceptionHandlerMiddleware(\n    RequestDelegate next,\n    IProblemDetailsService problemDetailsService,\n    ILogger&lt;GlobalExceptionHandlerMiddleware&gt; logger)\n{\n    public async Task InvokeAsync(HttpContext context)\n    {\n        try\n        {\n            await next(context);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, &quot;Unhandled exception occurred&quot;);\n\n            // Make sure to set the status code before writing to the response body\n            context.Response.StatusCode = ex switch\n            {\n                ApplicationException =&gt; StatusCodes.Status400BadRequest,\n                _ =&gt; StatusCodes.Status500InternalServerError\n            };\n\n            await problemDetailsService.TryWriteAsync(new ProblemDetailsContext\n            {\n                HttpContext = context,\n                Exception = ex,\n                ProblemDetails = new ProblemDetails\n                {\n                    Type = ex.GetType().Name,\n                    Title = &quot;An error occured&quot;,\n                    Detail = ex.Message\n                }\n            });\n        }\n    }\n}\n</code></pre>\n<p>This is much cleaner.\nWe're now using a standard format that API consumers expect, and we're not manually fiddling with JSON serialization.\nBut we're still stuck with that growing switch statement problem.\n<a href=\"https://milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis\"><strong>You can learn more about using Problem Details in .NET here</strong></a>.</p>\n<h2>The Modern Way: IExceptionHandler</h2>\n<p>ASP.NET Core 8 introduced <code>IExceptionHandler</code>, and it's a game-changer.\nInstead of one massive middleware handling everything, we can create focused handlers for specific exception types.</p>\n<p>Here's how it works:</p>\n<pre><code class=\"language-csharp\">internal sealed class GlobalExceptionHandler(\n    IProblemDetailsService problemDetailsService,\n    ILogger&lt;GlobalExceptionHandler&gt; logger) : IExceptionHandler\n{\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        logger.LogError(exception, &quot;Unhandled exception occurred&quot;);\n\n        httpContext.Response.StatusCode = exception switch\n        {\n            ApplicationException =&gt; StatusCodes.Status400BadRequest,\n            _ =&gt; StatusCodes.Status500InternalServerError\n        };\n\n        return await problemDetailsService.TryWriteAsync(new ProblemDetailsContext\n        {\n            HttpContext = httpContext,\n            Exception = exception,\n            ProblemDetails = new ProblemDetails\n            {\n                Type = exception.GetType().Name,\n                Title = &quot;An error occured&quot;,\n                Detail = exception.Message\n            }\n        });\n    }\n}\n</code></pre>\n<p>The key here is the return value.\nIf your handler can deal with the exception, return <code>true</code>.\nIf not, return <code>false</code> and let the next handler try.</p>\n<p>Don't forget to register it with DI and the request pipeline:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();\nbuilder.Services.AddProblemDetails();\n\n// And in your pipeline\napp.UseExceptionHandler();\n</code></pre>\n<p>This approach is so much cleaner.\nEach handler has one job, and the code is easy to test and maintain.</p>\n<h2>Chaining Exception Handlers</h2>\n<p>You can chain multiple <a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\"><strong>exception handlers</strong></a> together, and they'll run in the order you register them.\nASP.NET Core will use the first one that returns <code>true</code> from <code>TryHandleAsync</code>.</p>\n<p>Example: One for validation errors, one global fallback.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddExceptionHandler&lt;ValidationExceptionHandler&gt;();\nbuilder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();\n</code></pre>\n<p>Let's say you're using <a href=\"https://fluentvalidation.net/\">FluentValidation</a> (and you should be).\nHere's a complete setup:</p>\n<pre><code class=\"language-csharp\">internal sealed class ValidationExceptionHandler(\n    IProblemDetailsService problemDetailsService,\n    ILogger&lt;ValidationExceptionHandler&gt; logger) : IExceptionHandler\n{\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        if (exception is not ValidationException validationException)\n        {\n            return false;\n        }\n\n        logger.LogError(exception, &quot;Unhandled exception occurred&quot;);\n\n        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;\n        var context = new ProblemDetailsContext\n        {\n            HttpContext = httpContext,\n            Exception = exception,\n            ProblemDetails = new ProblemDetails\n            {\n                Detail = &quot;One or more validation errors occurred&quot;,\n                Status = StatusCodes.Status400BadRequest\n            }\n        };\n\n        var errors = validationException.Errors\n            .GroupBy(e =&gt; e.PropertyName)\n            .ToDictionary(\n                g =&gt; g.Key.ToLowerInvariant(),\n                g =&gt; g.Select(e =&gt; e.ErrorMessage).ToArray()\n            );\n        context.ProblemDetails.Extensions.Add(&quot;errors&quot;, errors);\n\n        return await problemDetailsService.TryWriteAsync(context);\n    }\n}\n</code></pre>\n<p>And in your app, just throw like this:</p>\n<pre><code class=\"language-csharp\">// In your controller or service - IValidator&lt;CreateUserRequest&gt;\npublic async Task&lt;IActionResult&gt; CreateUser(CreateUserRequest request)\n{\n    await _validator.ValidateAndThrowAsync(request);\n\n    // Your business logic here\n}\n</code></pre>\n<p>The execution order is important.\nThe framework will try each handler in the order you registered them.\nSo put your most specific handlers first, and your catch-all handler last.</p>\n<h2>Summary</h2>\n<p>We've come a long way from the days of manually crafting error responses in middleware.\nThe evolution looks like this:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\"><strong>Middleware</strong></a>: Simple, works everywhere, but gets complex fast</li>\n<li><a href=\"https://milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis\"><strong>IProblemDetailsService</strong></a>: Standardizes response format, still manageable</li>\n<li><a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\"><strong>IExceptionHandler</strong></a>: Modern, testable, and scales beautifully</li>\n</ul>\n<p>For new projects, I'd go straight to <code>IExceptionHandler</code>.\nIt's cleaner, more maintainable, and gives you the flexibility to handle different exception types exactly how you want.</p>\n<p>The key takeaway?\nDon't let error handling be an afterthought.\nSet it up early, make it consistent, and your users (and your future self) will thank you when things inevitably go wrong.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-from-middleware-to-modern-handlers",
            "title": "Global Error Handling in ASP.NET Core: From Middleware to Modern Handlers",
            "summary": "Learn How to handle errors globally in ASP.NET Core using middleware, IProblemDetailsService, and the new IExceptionHandler in .NET 8.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_150.png",
            "date_modified": "2025-07-12T00:00:00.000Z",
            "date_published": "2025-07-12T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/using-dotnet-aspire-with-the-docker-publisher",
            "content_html": "<p>Aspire's Docker publisher, a preview package in mid 2025, turns the services you declare in C# into a <code>docker-compose.yml</code> and a matching <code>.env</code> file.\nYou call <code>AddDockerComposeEnvironment</code> in the AppHost, then run <code>aspire publish</code> to generate the artifacts.\nDeploying them to a VPS is still your job.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a> is one of the most exciting additions to the .NET ecosystem in years.</p>\n<p>It brings a fresh, modern approach to building cloud-native apps, with a focus on developer productivity,\ngreat defaults, and tight integration across your entire stack.</p>\n<p>One of the most asked-for features is the ability to publish your app to Docker Compose.\nThis is now available in the latest preview, and I'm excited to share how it works.</p>\n<p>In this post, I'll walk you through how I used Aspire's <strong>Docker publisher</strong> to spin up a demo app that includes an API, a Postgres database, and a Redis cache.\nEverything runs using Docker Compose, and Aspire generates the whole thing from C# code.</p>\n<p>I'll show you how to set it up, explain what it's doing behind the scenes,\nand give you a glimpse into how easy it is to take that setup and run it on a VPS or cloud server.</p>\n<h2>The Demo App</h2>\n<p>The app is intentionally simple, just enough to demonstrate how Aspire wires things together.</p>\n<ul>\n<li>API project: a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>minimal .NET Web API</strong></a></li>\n<li>Postgres: used for data storage</li>\n<li>Redis: used for caching</li>\n</ul>\n<p>In a traditional setup, you'd manually connect these services, manage configuration files, handle environment variables,\nand write a <code>docker-compose.yml</code> from scratch.</p>\n<p>With Aspire, you declare everything in C# inside the AppHost project.\nHere's a quick look at the setup:</p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\n// Enables Docker publisher\nbuilder.AddDockerComposeEnvironment(&quot;aspire-docker-demo&quot;);\n\nvar postgres = builder.AddPostgres(&quot;database&quot;)\n    .WithDataVolume();\n\nvar database = postgres.AddDatabase(&quot;demo-db&quot;);\n\nvar redis = builder.AddRedis(&quot;cache&quot;);\n\nvar webApi = builder.AddProject&lt;Projects.Web_Api&gt;(&quot;web-api&quot;)\n    .WithReference(database).WaitFor(postgres)\n    .WithReference(redis).WaitFor(redis);\n\nbuilder.Build().Run();\n</code></pre>\n<p>This gives you a development environment where Aspire runs Postgres and Redis in containers, and connects your API to them.</p>\n<p>The <code>AddDockerComposeEnvironment</code> method enables the Docker publisher.\nIt's available in the <code>Aspire.Hosting.Docker</code> NuGet package, which is currently in preview.</p>\n<pre><code class=\"language-powershell\">Install-Package Aspire.Hosting.Docker -Version 9.3.1-preview.1.25305.6\n</code></pre>\n<h2>Installing the Aspire CLI</h2>\n<p>To publish your app to Docker Compose, install the <strong>Aspire CLI</strong>:</p>\n<pre><code class=\"language-bash\">dotnet tool install --global aspire.cli --prerelease\n</code></pre>\n<p>Then you can run the <code>publish</code> command:</p>\n<pre><code class=\"language-bash\">aspire publish -o docker-compose-artifacts\n</code></pre>\n<p>This command will scan your solution for the Aspire project and generate a Docker Compose file and an <code>.env</code> file\nbased on the services you've defined.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_149/aspire_publish.png\" alt=\"Aspire publish command\">\n</div>\n<h2>The Docker Compose File</h2>\n<p>Let's examine what Aspire created for us:</p>\n<pre><code class=\"language-yml\">services:\n  database:\n    image: 'docker.io/library/postgres:17.4'\n    environment:\n      POSTGRES_HOST_AUTH_METHOD: 'scram-sha-256'\n      POSTGRES_INITDB_ARGS: '--auth-host=scram-sha-256 --auth-local=scram-sha-256'\n      POSTGRES_USER: 'postgres'\n      POSTGRES_PASSWORD: '${DATABASE_PASSWORD}'\n    ports:\n      - '8000:5432'\n    volumes:\n      - type: 'volume'\n        target: '/var/lib/postgresql/data'\n        source: 'aspire.apphost-1f0ed76b33-database-data'\n        read_only: false\n    networks:\n      - 'aspire'\n  redis:\n    image: 'docker.io/library/redis:7.4'\n    command:\n      - '-c'\n      - 'redis-server --requirepass $$REDIS_PASSWORD'\n    entrypoint:\n      - '/bin/sh'\n    environment:\n      REDIS_PASSWORD: '${REDIS_PASSWORD}'\n    ports:\n      - '8001:6379'\n    networks:\n      - 'aspire'\n  web-api:\n    image: '${WEB_API_IMAGE}'\n    environment:\n      OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: 'true'\n      OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: 'true'\n      OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: 'in_memory'\n      ASPNETCORE_FORWARDEDHEADERS_ENABLED: 'true'\n      HTTP_PORTS: '8002'\n      ConnectionStrings__demo-db: 'Host=database;Port=5432;Username=postgres;Password=${DATABASE_PASSWORD};Database=demo-db'\n      ConnectionStrings__redis: 'redis:6379,password=${REDIS_PASSWORD}'\n    ports:\n      - '8003:8002'\n      - '8005:8004'\n    depends_on:\n      database:\n        condition: 'service_started'\n      redis:\n        condition: 'service_started'\n    networks:\n      - 'aspire'\nnetworks:\n  aspire:\n    driver: 'bridge'\nvolumes:\n  aspire.apphost-1f0ed76b33-database-data:\n    driver: 'local'\n</code></pre>\n<p>This <code>docker-compose</code> file is generated from the C# code we wrote earlier.\nIt defines the services, their images, environment variables, ports, and dependencies.</p>\n<p>The <code>.env</code> file contains some of the configuration we need:</p>\n<pre><code class=\"language-txt\"># Parameter database-password\nDATABASE_PASSWORD=&lt;YOUR_STRONG_PASSWORD&gt;\n\n# Container image name for web-api\n# Change this to the imaage name in the container registry\nWEB_API_IMAGE=web-api:latest\n\n# Parameter redis-password\nREDIS_PASSWORD=&lt;YOUR_STRONG_PASSWORD&gt;\n</code></pre>\n<p>It contains the passwords for the database and Redis, as well as the image name for the API.\nYou'll need to replace the placeholders with actual values.\nFor the API image, you can build and tag the image yourself, or use a pre-built one from a registry.</p>\n<h2>Publishing and Running on a VPS</h2>\n<p>Aspire doesn't deploy the app for you, but it gives you everything you need.</p>\n<p>Once the Compose file is ready, deployment to a VPS is straightforward:</p>\n<ol>\n<li>Copy the artifacts to your server using (<code>scp</code> or <code>git</code>).</li>\n<li>SSH into the VPS.</li>\n<li>Run <code>docker compose up -d</code> inside the artifact directory.</li>\n</ol>\n<p>Make sure <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker</strong></a> and Docker Compose are installed on the server.</p>\n<p>You can expose ports with a reverse proxy like Nginx or Caddy, and secure it with HTTPS using Let's Encrypt.</p>\n<p>I'll cover this in more detail in a future post.</p>\n<h2>Wrapping Up</h2>\n<p>.NET Aspire with Docker Compose provides a smooth developer experience and a simple way to deploy full-stack apps.</p>\n<p>You define everything in C#, test it locally, and publish it with one command.\nNo need to write Compose files or manage infrastructure manually.\nThis opens up new possibilities for deploying and managing cloud-native apps.\nYou're not locked into a specific cloud provider, and you can easily move between environments.</p>\n<p>Can I just say how much I love this?\nI'm really excited about the future of cloud-native development with .NET and Aspire.</p>\n<p>If you liked this article and want to learn how I structure larger apps, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.\nIt walks you through building clean, scalable .NET applications with strong internal boundaries and maintainability in mind.</p>\n<p>Thanks for reading and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/using-dotnet-aspire-with-the-docker-publisher",
            "title": "Using .NET Aspire With the Docker Publisher",
            "summary": "A practical walkthrough of using .NET Aspire's Docker publisher to generate Docker Compose files from C# code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_149.png",
            "date_modified": "2025-07-05T00:00:00.000Z",
            "date_published": "2025-07-05T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/testcontainers-best-practices-dotnet-integration-testing",
            "content_html": "<p>Testcontainers runs real Postgres and Redis containers for your integration tests instead of mocks or in-memory fakes.\nKeep them reliable by starting and stopping containers from <code>IAsyncLifetime</code> on your <code>WebApplicationFactory</code>, passing connection strings through <code>UseSetting</code> because the ports are dynamic, and pinning image tags like <code>postgres:17</code>.</p>\n<p>Integration tests with Testcontainers are powerful, but they can quickly become a maintenance nightmare if you don't follow the right patterns.</p>\n<p>I've seen teams struggle with flaky tests, slow test suites, and configuration headaches that could have been avoided with better practices from the start.</p>\n<p>Today, I'll show you the patterns that make <a href=\"https://testcontainers.com/\">Testcontainers</a> tests reliable, fast, and easy to maintain.</p>\n<h2>How Testcontainers Changes Integration Testing</h2>\n<p>Traditional <a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>integration tests</strong></a> often rely on shared test databases or in-memory alternatives that don't match production behavior.\nYou either deal with test pollution between runs or sacrifice realism for speed.</p>\n<p>Testcontainers solves this by spinning up real <a href=\"https://www.docker.com/\">Docker</a> containers for your dependencies.\nYour tests run against actual PostgreSQL, Redis, or any other service you use in production.\nWhen tests complete, containers are destroyed, giving you a clean slate every time.</p>\n<p>The magic happens through Docker's API.\nTestcontainers manages the entire lifecycle: pulling images, starting containers, waiting for readiness, and cleanup.\nYour test code just needs to know how to connect.</p>\n<h2>Prerequisites</h2>\n<p>First, make sure you have the necessary packages:</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.AspNetCore.Mvc.Testing\nInstall-Package Testcontainers.PostgreSql\nInstall-Package Testcontainers.Redis\n</code></pre>\n<p>If you want to learn more about the basic setup, check out my article on\n<a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>integrating testing with Testcontainers</strong></a>.</p>\n<h2>Creating Test Containers</h2>\n<p>Here's how to set up your containers with proper configuration:</p>\n<pre><code class=\"language-csharp\">PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()\n    .WithImage(&quot;postgres:17&quot;)\n    .WithDatabase(&quot;devhabit&quot;)\n    .WithUsername(&quot;postgres&quot;)\n    .WithPassword(&quot;postgres&quot;)\n    .Build();\n\nRedisContainer _redisContainer = new RedisBuilder()\n    .WithImage(&quot;redis:latest&quot;)\n    .Build();\n</code></pre>\n<p>To start and stop containers cleanly across your test suite, implement <code>IAsyncLifetime</code> in your <code>WebApplicationFactory</code>:</p>\n<pre><code class=\"language-csharp\">public sealed class IntegrationTestWebAppFactory : WebApplicationFactory&lt;Program&gt;, IAsyncLifetime\n{\n    public async Task InitializeAsync()\n    {\n        await _postgresContainer.StartAsync();\n        await _redisContainer.StartAsync();\n        // Start other dependencies here\n    }\n\n    public async Task DisposeAsync()\n    {\n        await _postgresContainer.StopAsync();\n        await _redisContainer.StopAsync();\n    }\n}\n</code></pre>\n<p>This ensures containers are ready before tests run and cleaned up afterward.\nThis means no leftover Docker state or race conditions.</p>\n<p><strong>A tip</strong>: pin your image versions (like <code>postgres:17</code>) to avoid surprises from upstream changes</p>\n<p>I learned this the hard way when a minor version update caused my tests to fail unexpectedly.</p>\n<h2>Pass Configuration to Your App</h2>\n<p>The biggest mistake I see is hardcoding connection strings.\nTestcontainers assigns dynamic ports.\nDon't hardcode anything.</p>\n<p>Instead, inject values via <code>WebApplicationFactory.ConfigureWebHost</code>:</p>\n<pre><code class=\"language-csharp\">protected override void ConfigureWebHost(IWebHostBuilder builder)\n{\n    builder.UseSetting(&quot;ConnectionStrings:Database&quot;, _postgresContainer.GetConnectionString());\n    builder.UseSetting(&quot;ConnectionStrings:Redis&quot;, _redisContainer.GetConnectionString());\n}\n</code></pre>\n<p>The key is to use the <code>UseSetting</code> method to pass connection strings dynamically.\nIt also avoids any race conditions or conflicts with other tests that might run in parallel.</p>\n<p>This ensures your tests always connect to the right ports, regardless of what Docker assigns.</p>\n<p>There's no need to remove services from the service collection or manually configure them (contrary to what you might find online).\nJust set the connection strings, and your application will use them automatically.</p>\n<h2>Share Expensive Setup with xUnit Collection Fixtures</h2>\n<p>What's a test fixture?\nA <strong>fixture</strong> is a shared context for your tests, allowing you to set up expensive resources like databases or message brokers once and reuse them across multiple tests.</p>\n<p>This is where most teams get tripped up.\nThe choice between class and collection fixtures affects both test performance and isolation.</p>\n<p><strong>Class Fixture</strong> - One container per test class:</p>\n<p>Use class fixtures when tests modify global state or when debugging test interactions becomes difficult.</p>\n<pre><code class=\"language-csharp\">public class AddItemToCartTests : IClassFixture&lt;DevHabitWebAppFactory&gt;\n{\n    private readonly DevHabitWebAppFactory _factory;\n\n    public AddItemToCartTests(DevHabitWebAppFactory factory)\n    {\n        _factory = factory;\n    }\n\n    [Fact]\n    public async Task Should_ReturnFailure_WhenNotEnoughQuantity() { ... }\n}\n</code></pre>\n<p><strong>Collection Fixture</strong> - One container shared across multiple test classes:</p>\n<p>Use collection fixtures when your tests don't modify shared state or when you can reliably clean up between tests.</p>\n<pre><code class=\"language-csharp\">[CollectionDefinition(nameof(IntegrationTestCollection))]\npublic sealed class IntegrationTestCollection : ICollectionFixture&lt;DevHabitWebAppFactory&gt;\n{\n}\n</code></pre>\n<p>Then apply it to your test classes:</p>\n<pre><code class=\"language-csharp\">[Collection(nameof(IntegrationTestCollection))]\npublic class AddItemToCartTests : IntegrationTestFixture\n{\n    public AddItemToCartTests(DevHabitWebAppFactory factory) : base(factory) { }\n\n    [Fact]\n    public async Task Should_ReturnFailure_WhenNotEnoughQuantity()\n    {\n        Guid customerId = await Sender.CreateCustomerAsync(Guid.NewGuid());\n        var command = new AddItemToCartCommand(customerId, ticketTypeId, Quantity + 1);\n\n        Result result = await Sender.Send(command);\n\n        result.Error.Should().Be(TicketTypeErrors.NotEnoughQuantity(Quantity));\n    }\n}\n</code></pre>\n<p>When to use which:</p>\n<ul>\n<li>Class fixtures when you need full isolation between test classes (slower but safer)</li>\n<li>Collection fixtures when test classes don't interfere with each other (faster but requires discipline)</li>\n</ul>\n<p>With collection fixtures, you have to take care of cleaning up any state that might persist between tests.\nThis could include resetting databases, clearing caches, or removing test data.\nIf you don't do this, you risk tests affecting each other, leading to flaky results.</p>\n<h2>Utility Methods for Auth and Cleanup</h2>\n<p>Your fixture can expose helpers to simplify test writing:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;HttpClient&gt; CreateAuthenticatedClientAsync() { ... }\n\nprotected async Task CleanupDatabaseAsync() { ... }\n</code></pre>\n<p>These methods can handle authentication setup and database cleanup, so you don't have to repeat boilerplate code in every test.\nThis lets your test code focus on assertions, not setup.</p>\n<h2>Writing Maintainable Integration Tests</h2>\n<p>With the infrastructure properly configured, your actual tests should focus on business logic:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Should_ReturnFailure_WhenNotEnoughQuantity()\n{\n    //Arrange\n    Guid customerId = await Sender.CreateCustomerAsync(Guid.NewGuid());\n    var eventId = Guid.NewGuid();\n    var ticketTypeId = Guid.NewGuid();\n\n    await Sender.CreateEventWithTicketTypeAsync(eventId, ticketTypeId, Quantity);\n\n    var command = new AddItemToCartCommand(customerId, ticketTypeId, Quantity + 1);\n\n    //Act\n    Result result = await Sender.Send(command);\n\n    //Assert\n    result.Error.Should().Be(TicketTypeErrors.NotEnoughQuantity(Quantity));\n}\n</code></pre>\n<p>Notice how the tests focus on business rules rather than infrastructure concerns.\nThe container complexity is hidden behind well-designed base classes and helper methods.\nYou're not mocking Postgres or Redis, you're testing real behavior.</p>\n<h2>Conclusion</h2>\n<p>Testcontainers transforms integration testing by giving you the confidence that comes from testing against real dependencies.\nNo more wondering if your in-memory database behavior matches production, or dealing with shared test environments that break when someone else runs their tests.</p>\n<p>Start simple: pick one integration test that currently uses mocks or in-memory databases, and convert it to use Testcontainers.\nYou'll immediately notice the difference in confidence when that test passes.\nThen gradually expand to cover your critical business flows.</p>\n<p>If you want to learn how to structure your applications for testability from day one,\nmy <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> course covers integration testing with Testcontainers alongside domain modeling,\nAPI design, and the architectural decisions that make applications maintainable over time.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/testcontainers-best-practices-dotnet-integration-testing",
            "title": "Testcontainers Best Practices for .NET Integration Testing",
            "summary": "Integration tests shouldn't rely on external infrastructure, but they also shouldn't mock everything away.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_148.png",
            "date_modified": "2025-06-28T00:00:00.000Z",
            "date_published": "2025-06-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/monitoring-dotnet-applications-with-opentelemetry-and-grafana",
            "content_html": "<p>To monitor a .NET application with OpenTelemetry and Grafana, install the OpenTelemetry hosting and instrumentation packages, call <code>AddOpenTelemetry()</code> in <code>Program.cs</code>, and point the OTLP exporter at your Grafana Cloud stack with three settings: endpoint, protocol, and an authorization header.\nTraces and logs then show up in Grafana, with automatic correlation between them.</p>\n<p>Your .NET application is running in production, but you're flying blind.</p>\n<p>When something breaks, you're stuck digging through logs, guessing at performance bottlenecks,\nand trying to piece together what actually happened across your distributed system.</p>\n<p>That ends today.</p>\n<p><a href=\"https://grafana.com/\">Grafana</a> is a complete observability platform that unifies <strong>metrics, logs, and traces</strong> in one place.</p>\n<p>With Grafana, you get:</p>\n<ul>\n<li><strong>Unified dashboards</strong> that combine metrics, logs, and traces</li>\n<li><strong>Advanced alerting</strong> that actually works when things go wrong</li>\n<li><strong>Deep trace analysis</strong> to understand request flows across services</li>\n<li><strong>Log correlation</strong> that connects your traces to the exact log entries that matter</li>\n</ul>\n<p><a href=\"https://grafana.com/products/cloud/\">Grafana Cloud</a> makes this even easier.\nNo infrastructure to manage, automatic scaling, and built-in integrations with <a href=\"https://opentelemetry.io/\">OpenTelemetry</a>.\nThere's a generous free tier that allows you to get started without any upfront costs.</p>\n<p>When you combine Grafana with OpenTelemetry, you get vendor-neutral observability that actually delivers insights instead of just pretty charts.</p>\n<h2>Setting Up OpenTelemetry in .NET</h2>\n<p>First, install the core OpenTelemetry packages:</p>\n<pre><code class=\"language-powershell\">Install-Package OpenTelemetry.Extensions.Hosting\nInstall-Package OpenTelemetry.Exporter.OpenTelemetryProtocol\nInstall-Package OpenTelemetry.Instrumentation.AspNetCore\nInstall-Package OpenTelemetry.Instrumentation.Http\n</code></pre>\n<p>You can also add instrumentation for other libraries as needed:</p>\n<pre><code class=\"language-powershell\">Install-Package OpenTelemetry.Instrumentation.EntityFrameworkCore\nInstall-Package OpenTelemetry.Instrumentation.StackExchangeRedis\nInstall-Package Npgsql.OpenTelemetry\n</code></pre>\n<p>Configure OpenTelemetry in your <code>Program.cs</code>:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services\n    .AddOpenTelemetry()\n    .ConfigureResource(resource =&gt; resource.AddService(serviceName))\n    .WithTracing(tracing =&gt;\n    {\n        tracing\n            .AddAspNetCoreInstrumentation()\n            .AddHttpClientInstrumentation()\n            .AddEntityFrameworkCoreInstrumentation()\n            .AddRedisInstrumentation()\n            .AddNpgsql();\n\n        tracing.AddOtlpExporter();\n    });\n\nbuilder.Logging.AddOpenTelemetry(logging =&gt;\n{\n    logging.IncludeScopes = true;\n    logging.IncludeFormattedMessage = true;\n\n    logging.AddOtlpExporter();\n});\n\nvar app = builder.Build();\n\n// Your app configuration...\n\napp.Run();\n</code></pre>\n<p>This configuration:</p>\n<ul>\n<li>Sets up <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>tracing with OpenTelemetry</strong></a></li>\n<li>Sets up automatic instrumentation for ASP.NET Core and HTTP requests</li>\n<li>Adds Entity Framework Core and Redis instrumentation</li>\n<li>Configures PostgreSQL instrumentation if you're using Npgsql</li>\n<li>Configures OTLP export for traces and logs (we can also <strong>add metrics</strong> later)</li>\n</ul>\n<h2>Configuring OTLP Export to Grafana Cloud</h2>\n<p>Get your Grafana Cloud credentials:</p>\n<ol>\n<li>\n<p>Log into <a href=\"https://grafana.com/auth/sign-up/create-user\">Grafana Cloud</a></p>\n</li>\n<li>\n<p>Go to <strong>My Account</strong> → <strong>Stack Details</strong></p>\n</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_setup.png\" alt=\"Grafana cloud stack details\">\n<ol start=\"3\">\n<li>Find your <strong>OTLP endpoint</strong> (looks like <code>https://otlp-gateway-prod-eu-west-2.grafana.net/otlp</code>)</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_setup_endpoint.png\" alt=\"Grafana cloud endpoint\">\n<ol start=\"4\">\n<li>Generate an <strong>API token</strong> with permissions</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_setup_token.png\" alt=\"Grafana cloud token\">\n<p>You should also see the environment variables you can use to configure OpenTelemetry:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_setup_env_vars.png\" alt=\"Grafana cloud environment variables\">\n<p>Configure the OTLP exporter in your <code>appsettings.json</code>:</p>\n<pre><code class=\"language-json\">{\n  &quot;OTEL_EXPORTER_OTLP_ENDPOINT&quot;: &quot;https://otlp-gateway-prod-eu-west-2.grafana.net/otlp&quot;,\n  &quot;OTEL_EXPORTER_OTLP_PROTOCOL&quot;: &quot;http/protobuf&quot;,\n  &quot;OTEL_EXPORTER_OTLP_HEADERS&quot;: &quot;Authorization=Basic &lt;your-base64-encoded-token&gt;&quot;\n}\n</code></pre>\n<p>You can also set these as environment variables in your hosting environment.</p>\n<h2>Viewing and Analyzing Data in Grafana</h2>\n<p>Start your application and generate some traffic.\nNow head to your Grafana Cloud instance.</p>\n<p><strong>Traces</strong></p>\n<p>If everything is set up correctly, you should see traces from your application in the <strong>Traces</strong> section.</p>\n<p>Here's an example of a trace view in Grafana Cloud.\nIt's a <code>POST users/register</code> request that contains multiple spans:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_trace_1.png\" alt=\"Grafana cloud trace example\">\n<p>Here's another example of a trace that includes messages sent to a message broker (like RabbitMQ or Kafka).</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_trace_2.png\" alt=\"Grafana cloud trace example\">\n<p><strong>Logs</strong></p>\n<p>You can also view logs in Grafana Cloud.</p>\n<p>Here's an example of a log view that shows multiple log entries for our application.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_logs_1.png\" alt=\"Grafana cloud logs example\">\n<p>You can drill down into individual log entries, filter by severity, and search for specific terms.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_147/grafana_logs_2.png\" alt=\"Grafana cloud logs example\">\n<p>OpenTelemetry automatically correlates your logs with traces.\nIn the trace detail view, click <strong>Logs</strong> to see all log entries that occurred during that request.</p>\n<p>Your logs will include trace and span IDs.</p>\n<h2>Conclusion</h2>\n<p>You now have full observability for your .NET application.</p>\n<p>When something goes wrong in production, you won't be guessing anymore.\nYou'll see exactly which requests were slow, what errors occurred, and how they propagated through your system.</p>\n<p>Grafana gives you the dashboards and alerting to catch problems before users do.\nOpenTelemetry gives you the detailed traces to understand exactly what happened.</p>\n<p>This setup scales from a single service to hundreds of microservices without changing your instrumentation code.\nAnd when your apps outgrow a single machine, the collector setup evolves too; I wrote about that next step in <a href=\"https://milanjovanovic.tech/blog/opentelemetry-collector-agent-gateway\"><strong>the agent + gateway collector pattern</strong></a>.</p>\n<p><strong>Ready to take your observability further?</strong></p>\n<p>This article showed you the basics, but modern applications need advanced patterns like distributed tracing across event-driven architectures,\ncustom instrumentation strategies, and observability-driven development practices.</p>\n<p>I cover all of this in-depth in my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course,\nwhere you'll learn how to implement OpenTelemetry across complex, event-driven systems that actually scale in production.</p>\n<p>That's all for today.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/monitoring-dotnet-applications-with-opentelemetry-and-grafana",
            "title": "Monitoring .NET Applications with OpenTelemetry and Grafana",
            "summary": "Instrumenting your .NET apps with OpenTelemetry is easy. But what about actually seeing those metrics and traces in action?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_147.png",
            "date_modified": "2025-06-21T00:00:00.000Z",
            "date_published": "2025-06-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/run-csharp-scripts-with-dotnet-run-app-no-project-files-needed",
            "content_html": "<p>.NET 10 (starting with Preview 4) lets you run a single C# file directly with <code>dotnet run app.cs</code>, no project file or <code>Main</code> method required.\nFile-level directives like <code>#:package</code> pull in NuGet packages, and <code>dotnet project convert</code> turns the script into a full project when it outgrows a single file.</p>\n<p><strong>.NET 10</strong> just got a whole lot more lightweight.</p>\n<p>You can now run a C# file directly with:</p>\n<pre><code class=\"language-bash\">dotnet run app.cs\n</code></pre>\n<p>That's it.\nNo <code>.csproj</code>.\nNo <code>Program.cs</code>.\nNo solution files.\nJust a single C# file.</p>\n<p>This new feature, introduced in <a href=\"https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/\">.NET 10 Preview 4</a>,\nis a big step toward making C# more script-friendly, especially for quick utilities, dev tooling, and CLI-based workflows.</p>\n<h2>Why This Matters</h2>\n<p>For years, C# has been perceived as heavyweight for small scripts.\nCompare that to Python, Bash, or even JavaScript, where you can just write a file and run it.</p>\n<p>That barrier is now gone.</p>\n<p>You can now:</p>\n<ul>\n<li>Write one-off scripts in <code>.cs</code> files</li>\n<li>Use top-level statements</li>\n<li>Reference NuGet packages inline</li>\n<li>Share minimal reproducible examples without scaffolding a project</li>\n</ul>\n<p>And it runs on <strong>any OS</strong> with the .NET SDK installed.</p>\n<h2>Minimal Example</h2>\n<p>Here's a simple script that prints today's date:</p>\n<pre><code class=\"language-csharp\">Console.WriteLine($&quot;Today is {DateTime.Now:dddd, MMM dd yyyy}&quot;);\n</code></pre>\n<p>Run it:</p>\n<pre><code class=\"language-bash\">dotnet run app.cs\n</code></pre>\n<p>Output:</p>\n<pre><code>Today is Saturday, Jun 14 2025\n</code></pre>\n<p>That's it.\nNo boilerplate, no boring <code>Main()</code> method.\nJust top-level programs and C# code.</p>\n<h2>Referencing NuGet Packages</h2>\n<p>Let's say you want to make an HTTP request using <code>Flurl.Http</code>.</p>\n<p>You can do this inline:</p>\n<pre><code class=\"language-csharp\">#:package Flurl.Http@4.0.2\n\nusing Flurl.Http;\n\nvar response = await &quot;https://api.github.com&quot;\n    .WithHeader(&quot;Accept&quot;, &quot;application/vnd.github.v3+json&quot;)\n    .WithHeader(&quot;User-Agent&quot;, &quot;dotnet-script&quot;)\n    .GetAsync();\n\nConsole.WriteLine($&quot;Status code: {response.StatusCode}&quot;);\n\nConsole.WriteLine(await response.GetJsonAsync&lt;object&gt;());\n</code></pre>\n<p>To run it:</p>\n<pre><code class=\"language-bash\">dotnet run fetch.cs\n</code></pre>\n<p>Behind the scenes, the compiler downloads and restores NuGet dependencies automatically.</p>\n<h2>Real-World Use Case: Seeding SQL Data</h2>\n<p>Here's a script I recently used to seed some test data into my Postgres database.</p>\n<pre><code class=\"language-csharp\">#:package Dapper@2.1.66\n#:package Npgsql@9.0.3\n\nusing Dapper;\nusing Npgsql;\n\nconst string connectionString = &quot;Host=localhost;Port=5432;Username=postgres;Password=postgres&quot;;\n\nusing var connection = new NpgsqlConnection(connectionString);\nawait connection.OpenAsync();\n\nusing var transaction = connection.BeginTransaction();\n\nConsole.WriteLine(&quot;Creating tables...&quot;);\n\nawait connection.ExecuteAsync(@&quot;\n    CREATE TABLE IF NOT EXISTS users (\n        id SERIAL PRIMARY KEY,\n        name TEXT NOT NULL\n    );\n&quot;);\n\nConsole.WriteLine(&quot;Inserting users...&quot;);\n\nfor (int i = 1; i &lt;= 10_000; i++)\n{\n    await connection.ExecuteAsync(\n        &quot;INSERT INTO users (name) VALUES (@Name);&quot;,\n        new { Name = $&quot;User {i}&quot; });\n\n    if (i % 1000 == 0)\n    {\n        Console.WriteLine($&quot;Inserted {i} users...&quot;);\n    }\n}\n\ntransaction.Commit();\n\nConsole.WriteLine(&quot;Done!&quot;);\n</code></pre>\n<p>Why did I write this as a script?\nI didn't want to clutter my app with throwaway seed logic.\nI just needed a quick way to populate my database with test data.\nThis script does exactly that, and I can run it with:</p>\n<pre><code class=\"language-bash\">dotnet run seed.cs\n</code></pre>\n<h2>File-Level Directives: The Magic Behind It</h2>\n<p>The real power comes from file-level directives.\nThese let you configure your app without leaving the .cs file:</p>\n<p><strong>Package References</strong></p>\n<pre><code class=\"language-csharp\">#:package Dapper@2.1.66\n#:package Npgsql@9.0.3\n</code></pre>\n<p><strong>SDK Selection</strong></p>\n<pre><code class=\"language-csharp\">#:sdk Microsoft.NET.Sdk.Web\n</code></pre>\n<p>This tells .NET to treat your file as a web application, enabling ASP.NET Core features:</p>\n<pre><code class=\"language-csharp\">#:sdk Microsoft.NET.Sdk.Web\n#:package Microsoft.AspNetCore.OpenApi@9.*\n\nvar builder = WebApplication.CreateBuilder();\n\nbuilder.Services.AddOpenApi();\n\nvar app = builder.Build();\n\napp.MapOpenApi();\n\napp.MapGet(&quot;/&quot;, () =&gt; &quot;Hello from a file-based API!&quot;);\napp.MapGet(&quot;/users/{id}&quot;, (int id) =&gt; new { Id = id, Name = $&quot;User {id}&quot; });\n\napp.Run();\n</code></pre>\n<p>You now have a running web API.\nNo project file.\nNo <code>Startup.cs</code>.\nJust C# that does what you want.</p>\n<p><strong>MSBuild Properties</strong></p>\n<p>You can also set MSBuild properties directly in the file:</p>\n<pre><code class=\"language-csharp\">#:property LangVersion preview\n#:property Nullable enable\n</code></pre>\n<h2>When Your Script Grows Up</h2>\n<p>The brilliant part? When your file-based app gets complex enough to need project structure, converting is seamless:</p>\n<pre><code class=\"language-bash\">dotnet project convert api.cs\n</code></pre>\n<p>This creates:</p>\n<ul>\n<li>A new folder named after your file</li>\n<li>A proper <code>.csproj</code> file with all your directives converted to MSBuild properties</li>\n<li>Your code moved to <code>api.cs</code> (or <code>Program.cs</code> if you prefer)</li>\n<li>Everything ready for full project development</li>\n</ul>\n<p>Given our API example above, the generated <code>.csproj</code> looks like:</p>\n<pre><code class=\"language-xml\">&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;\n    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;\n    &lt;Nullable&gt;enable&lt;/Nullable&gt;\n  &lt;/PropertyGroup&gt;\n\n  &lt;ItemGroup&gt;\n    &lt;PackageReference Include=&quot;Microsoft.AspNetCore.OpenApi&quot; Version=&quot;9.*&quot; /&gt;\n  &lt;/ItemGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>Your file-based app evolves naturally into a project-based app.\nNo need to rewrite or restructure everything.\nThis makes it easy to start small and grow as needed, without losing the simplicity of the initial script.</p>\n<h2>Takeaway</h2>\n<p>The bottom line is this: C# just became significantly more approachable.\nThe barrier to entry dropped from &quot;learn project files and MSBuild&quot; to &quot;write C# and run it.&quot;</p>\n<p>For experienced developers, this is a productivity boost for scripting and prototyping.\nFor newcomers, this removes the biggest stumbling block to getting started with C#.</p>\n<p>The best part?\nMicrosoft didn't create a separate scripting language or runtime.\nThey made regular C# easier to use.\nYour file-based apps are real .NET applications that can grow into full projects when needed.</p>\n<p>The ceremony is dead. Long live practical C#.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/run-csharp-scripts-with-dotnet-run-app-no-project-files-needed",
            "title": "Run C# Scripts With dotnet run app.cs (No Project Files Needed)",
            "summary": "With .NET 10, you can now run C# files directly. No project files, no Main method, just code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_146.png",
            "date_modified": "2025-06-14T00:00:00.000Z",
            "date_published": "2025-06-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/debunking-the-filter-early-join-later-sql-performance-myth",
            "content_html": "<p>No, filtering in a subquery before a JOIN does not make your SQL faster on a modern database.\nI tested both versions on PostgreSQL with 10,000 users and 5 million orders, and they produced identical execution plans, each running in about 685ms.\nThe cost-based optimizer rewrites both queries into the same plan, regardless of how you write them.</p>\n<p>I came across a Medium article with 700+ claps promoting this &quot;SQL performance trick&quot;:</p>\n<p><strong>&quot;Filter Early, JOIN Later&quot;</strong></p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_145/sql_performance_tip.png\" alt=\"SQL performance tip that doesn\">\n  </div>\n  <figcaption>Source: SQL Tricks that Cut My Query Time by 80%</figcaption>\n</figure>\n<p>The claim goes like this: instead of joining tables first and then filtering, you should filter in a subquery first, then join.</p>\n<p>The supposed benefit?</p>\n<blockquote>\n<p>The database filtered the smaller table first, then did the JOIN — saving time and memory.</p>\n</blockquote>\n<p>Here is the thing - this advice is <strong>completely wrong</strong> for modern databases.</p>\n<p>Let me show you why with actual data.</p>\n<h2>The Supposed &quot;Optimization&quot;</h2>\n<p>The article shows two queries. Here is the &quot;bad&quot; version:</p>\n<pre><code class=\"language-sql\">SELECT *\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE o.total &gt; 500;\n</code></pre>\n<p>And the &quot;optimized&quot; version:</p>\n<pre><code class=\"language-sql\">SELECT *\nFROM (\n  SELECT * FROM orders WHERE total &gt; 500\n) o\nJOIN users u ON u.id = o.user_id;\n</code></pre>\n<p>The claim is that the second query is faster because it &quot;filters first, then joins.&quot;</p>\n<p>Sounds logical, right?\n<strong>Wrong.</strong></p>\n<h2>Testing with Real Data</h2>\n<p>I tested both queries on a PostgreSQL database with:</p>\n<ul>\n<li>10,000 users</li>\n<li>5,000,000 orders (500 per user)</li>\n<li>Filtering for orders &gt; $500</li>\n</ul>\n<p>Let me run <strong>EXPLAIN ANALYZE</strong> on both queries to see what actually happens.</p>\n<h2>The Results</h2>\n<p>Here are the execution plans for both queries:</p>\n<p><strong>&quot;Bad&quot; Query Execution Plan:</strong></p>\n<pre><code>Hash Join  (cost=280.00..96321.92 rows=2480444 width=27) (actual time=1.014..641.202 rows=2499245 loops=1)\n  Hash Cond: (o.user_id = u.id)\n  -&gt;  Seq Scan on orders o  (cost=0.00..89528.00 rows=2480444 width=14) (actual time=0.006..368.857 rows=2499245 loops=1)\n        Filter: (total &gt; '500'::numeric)\n        Rows Removed by Filter: 2500755\n  -&gt;  Hash  (cost=155.00..155.00 rows=10000 width=13) (actual time=0.998..0.999 rows=10000 loops=1)\n        Buckets: 16384  Batches: 1  Memory Usage: 577kB\n        -&gt;  Seq Scan on users u  (cost=0.00..155.00 rows=10000 width=13) (actual time=0.002..0.341 rows=10000 loops=1)\nPlanning Time: 0.121 ms\nExecution Time: 685.818 ms\n</code></pre>\n<p><strong>&quot;Optimized&quot; Query Execution Plan:</strong></p>\n<pre><code>Hash Join  (cost=280.00..96321.92 rows=2480444 width=27) (actual time=1.019..640.613 rows=2499245 loops=1)\n  Hash Cond: (orders.user_id = u.id)\n  -&gt;  Seq Scan on orders  (cost=0.00..89528.00 rows=2480444 width=14) (actual time=0.005..368.260 rows=2499245 loops=1)\n        Filter: (total &gt; '500'::numeric)\n        Rows Removed by Filter: 2500755\n  -&gt;  Hash  (cost=155.00..155.00 rows=10000 width=13) (actual time=1.004..1.005 rows=10000 loops=1)\n        Buckets: 16384  Batches: 1  Memory Usage: 577kB\n        -&gt;  Seq Scan on users u  (cost=0.00..155.00 rows=10000 width=13) (actual time=0.003..0.348 rows=10000 loops=1)\nPlanning Time: 0.118 ms\nExecution Time: 685.275 ms\n</code></pre>\n<p><strong>The execution plans are identical.</strong></p>\n<p>Both queries took ~685ms.\nThe &quot;optimization&quot; did absolutely nothing.</p>\n<p>Here's the simplified execution plan, where I removed some details:</p>\n<pre><code>Hash Join\n  Hash Cond: (o.user_id = u.id)\n  -&gt;  Seq Scan on orders o\n        Filter: (total &gt; '500'::numeric)\n        Rows Removed by Filter\n  -&gt;  Hash\n        -&gt;  Seq Scan on users u\n</code></pre>\n<p>The core operations are:</p>\n<ol>\n<li>Sequential Scan on <code>orders</code> table with filter applied</li>\n<li>Sequential Scan on <code>users</code> table</li>\n<li>Hash operation to build hash table from <code>users</code></li>\n<li>Hash Join using the hash condition on <code>user_id</code></li>\n</ol>\n<h2>Query Optimizers Are Smarter Than You</h2>\n<p>Modern databases use <strong>cost-based optimizers</strong>.\nHere is what happens when you run a query:</p>\n<ol>\n<li><strong>Parser</strong> turns your SQL into an abstract syntax tree</li>\n<li><strong>Optimizer</strong> rewrites your query into the most efficient form</li>\n<li><strong>Executor</strong> runs the optimized plan</li>\n</ol>\n<p>The optimizer looks at your query and says:\n&quot;I don't care how you wrote this. I will figure out the best way to execute it.&quot;</p>\n<p>Both of our queries get rewritten to the same optimal plan:</p>\n<ul>\n<li>Filter the orders table first (because that eliminates rows early)</li>\n<li>Build a hash table from users (the smaller table)</li>\n<li>Hash join the filtered orders with users</li>\n</ul>\n<p><strong>The optimizer already does the &quot;optimization&quot; automatically.</strong></p>\n<p>Your manual subquery does not make it faster - it just makes your SQL harder to read.</p>\n<h2>How Cost-Based Optimization Works</h2>\n<p>The query optimizer has statistics about your tables:</p>\n<ul>\n<li>Row counts</li>\n<li>Data distribution</li>\n<li>Index availability</li>\n<li>Column selectivity</li>\n</ul>\n<p>It uses these stats to estimate the cost of different execution strategies:</p>\n<ul>\n<li>Which table to scan first</li>\n<li>Which join algorithm to use (hash, nested loop, merge)</li>\n<li>When to apply filters</li>\n<li>Which indexes to use</li>\n</ul>\n<p>Then it picks the cheapest plan.\nYour well-intentioned manual &quot;optimization&quot; gets ignored because the optimizer knows better.</p>\n<h2>Summary</h2>\n<p>The &quot;Filter Early, JOIN Later&quot; advice is a relic from ancient database systems that did not have sophisticated optimizers.</p>\n<p>Modern databases like PostgreSQL, MySQL, and SQL Server already do predicate pushdown and join reordering automatically.\nYour manual &quot;optimizations&quot; are pointless and make code harder to maintain.</p>\n<p>Write clear, readable SQL.\nLet the optimizer do its job.</p>\n<p><strong>The real lesson?</strong>\nStop believing every performance tip you read online.\nUse <code>EXPLAIN ANALYZE</code> to understand what your database is actually doing.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/debunking-the-filter-early-join-later-sql-performance-myth",
            "title": "Debunking the \"Filter Early, JOIN Later\" SQL Performance Myth",
            "summary": "That viral SQL performance tip about filtering before joining? It is complete nonsense. Here is why query optimizers make it irrelevant.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_145.png",
            "date_modified": "2025-06-07T00:00:00.000Z",
            "date_published": "2025-06-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/yarp-vs-nginx-a-quick-performance-comparison",
            "content_html": "<p>Tuned properly, Nginx edges out YARP on raw throughput: about 46k versus 36k requests per second at 200 virtual users in my tests.\nAgainst Nginx's default configuration, YARP handled almost 3.6x more requests.\nYARP still wins on .NET integration, so pick based on whether throughput or developer experience matters more to your team.</p>\n<p>When you're building .NET applications, choosing the right <strong>reverse proxy</strong> can make a huge difference.\nTwo popular options keep coming up: Microsoft's <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>YARP</strong></a> (Yet Another Reverse Proxy)\nand the tried-and-true <a href=\"https://nginx.org/\"><strong>Nginx</strong></a>.</p>\n<p>Here's the thing - everyone talks about which one is &quot;better,&quot; but rarely do you see actual numbers.\nSo I decided to put both through the same tests and see what happens.</p>\n<p>I'll test both proxies using the exact same API, same hardware, and same load testing approach.\nNo bias, just data.</p>\n<h2>The Test API</h2>\n<p>I kept the test API super simple on purpose.\nThis way, we're measuring proxy performance, not how fast the backend can process complex requests:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\nvar app = builder.Build();\n\napp.MapGet(&quot;/hello&quot;, () =&gt;\n{\n    return Results.Ok(&quot;Hello world!&quot;);\n});\n\napp.Run();\n</code></pre>\n<p>This basic endpoint means we're testing the proxy itself, not waiting for complex business logic to run.</p>\n<h2>YARP Configuration</h2>\n<p>YARP is pretty nice to work with if you're already in the .NET world. The setup is straightforward:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services\n    .AddReverseProxy()\n    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;));\n\nvar app = builder.Build();\napp.MapReverseProxy();\napp.Run();\n</code></pre>\n<p>It's equally simple to configure <a href=\"https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing\"><strong>load balancing</strong></a>\nor <a href=\"https://milanjovanovic.tech/blog/implementing-api-gateway-authentication-with-yarp\"><strong>authentication</strong></a>.</p>\n<p>The routing setup is clean and uses a <code>**catch-all</code> pattern to forward everything:</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;default&quot;: {\n        &quot;ClusterId&quot;: &quot;hello&quot;,\n        &quot;Match&quot;: { &quot;Path&quot;: &quot;{**catch-all}&quot; }\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;hello&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://hello.api:8080&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<h2>Nginx Setup</h2>\n<p>For Nginx, I went with <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker</strong></a> to keep things simple.\nThe configuration does the same job as YARP:</p>\n<pre><code class=\"language-yml\">nginx.proxy:\n  image: nginx:alpine\n  ports:\n    - '3001:80'\n  volumes:\n    - ./nginx-proxy.conf:/etc/nginx/nginx.conf:ro\n  depends_on:\n    - hello.api\n</code></pre>\n<p>The Nginx config does exactly what YARP does - just with different syntax:</p>\n<pre><code class=\"language-nginx\">events {}\n\nhttp {\n    upstream backend {\n        server hello.api:8080;\n    }\n\n    server {\n        listen 80;\n        location / {\n            proxy_pass http://backend;\n        }\n    }\n}\n</code></pre>\n<h2>Full Docker Compose Setup</h2>\n<p>Here's the complete <code>docker-compose.yml</code> that ties everything together:</p>\n<pre><code class=\"language-yml\">services:\n  hello.api:\n    image: ${DOCKER_REGISTRY-}helloapi\n    build:\n      context: .\n      dockerfile: Hello.Api/Dockerfile\n\n  yarp.proxy:\n    image: ${DOCKER_REGISTRY-}yarpproxy\n    build:\n      context: .\n      dockerfile: Yarp.Proxy/Dockerfile\n    ports:\n      - 3000:8080\n    depends_on:\n      - hello.api\n\n  nginx.proxy:\n    image: nginx:alpine\n    ports:\n      - '3001:80'\n    volumes:\n      - ./nginx-proxy.conf:/etc/nginx/nginx.conf:ro\n    depends_on:\n      - hello.api\n</code></pre>\n<h2>Load Testing with k6</h2>\n<p>I used <strong>k6</strong> to hit both proxies with the same load patterns.\nI repeated the test with different numbers of virtual users (VUs) to see how each proxy handles increasing traffic.\nThis keeps things fair - same test, same conditions:</p>\n<p><strong>YARP Test Script</strong>:</p>\n<pre><code class=\"language-js\">import http from 'k6/http';\nimport { check } from 'k6';\n\nexport let options = {\n  scenarios: {\n    yarp: {\n      executor: 'per-vu-iterations',\n      vus: 200, // 10, 50, 100, 200\n      iterations: 1000,\n      exec: 'testYarp',\n      startTime: '0s'\n    }\n  }\n};\n\nexport function testYarp() {\n  let res = http.get('http://localhost:3000/hello');\n  check(res, {\n    'YARP: status 200': (r) =&gt; r.status === 200\n  });\n}\n</code></pre>\n<p><strong>Nginx Test Script</strong>:</p>\n<pre><code class=\"language-js\">import http from 'k6/http';\nimport { check } from 'k6';\n\nexport let options = {\n  scenarios: {\n    nginx: {\n      executor: 'per-vu-iterations',\n      vus: 200, // 10, 50, 100, 200\n      iterations: 1000,\n      exec: 'testNginx',\n      startTime: '0s'\n    }\n  }\n};\n\nexport function testNginx() {\n  let res = http.get('http://localhost:3001/hello');\n  check(res, {\n    'NGINX: status 200': (r) =&gt; r.status === 200\n  });\n}\n</code></pre>\n<h2>Performance Results</h2>\n<p>Here's where things get interesting.\nThe numbers show a clear pattern:</p>\n<pre><code>| VUs  | YARP RPS | NGINX RPS | YARP p90 Latency (ms) | NGINX p90 Latency (ms) | YARP p95 Latency (ms) | NGINX p95 Latency (ms) |\n|------|----------|-----------|-----------------------|------------------------|-----------------------|------------------------|\n| 10   | 12692    | 9756      | 1.04                  | 1.10                   | 1.06                  | 1.52                   |\n| 50   | 27080    | 10614     | 2.70                  | 5.23                   | 3.18                  | 5.68                   |\n| 100  | 32432    | 10324     | 4.66                  | 10.61                  | 5.43                  | 10.96                  |\n| 200  | 36662    | 10169     | 7.77                  | 21.23                  | 8.81                  | 21.92                  |\n</code></pre>\n<p><strong>Request per second (RPS)</strong> is how many requests each proxy handled per second.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/rps_comparison.png\" alt=\"YARP vs Nginx RPS comparison\">\n<p><strong>p90 latency</strong> is the time it took for 90% of requests to complete.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/p90_comparison.png\" alt=\"YARP vs Nginx p90 latency comparison\">\n<p><strong>p95 latency</strong> is the time it took for 95% of requests to complete.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/p95_comparison.png\" alt=\"YARP vs Nginx p95 latency comparison\">\n<h3>Throughput Analysis</h3>\n<p>YARP really shines here.\nIt handles way more requests - almost 3.6x more at 200 users.\nWhat's cool is how it scales up as you add more load.\nNginx stays pretty much flat around 10k requests per second, but YARP keeps climbing from 12k all the way to 36k.</p>\n<h3>Latency Comparison</h3>\n<p>The latency story is even more impressive for YARP.\nAt 200 users, YARP keeps response times under 8ms while Nginx hits 21ms.\nThat's a big difference when you're trying to keep your app fast.</p>\n<h2>Hold Up - That's Not Fair</h2>\n<p>Looking at these results, we're missing something important: <strong>this comparison isn't fair to Nginx</strong>.</p>\n<p>The default Nginx configuration I used is fine for basic setups, but it's not optimized for high-throughput scenarios.\nNginx uses conservative defaults that work everywhere but don't push performance limits.</p>\n<p>So let me fix the Nginx configuration and re-run the tests.</p>\n<p>Here's the updated Nginx config with some tweaks to improve performance:</p>\n<pre><code class=\"language-nginx\">worker_processes auto;\n\nevents {\n    worker_connections 65536;\n    multi_accept on;\n    use epoll;\n}\n\nhttp {\n    sendfile on;\n    tcp_nopush on;\n    tcp_nodelay on;\n    keepalive_timeout 30;\n    keepalive_requests 1000;\n    types_hash_max_size 4096;\n\n    upstream backend {\n        server hello.api:8080;\n        keepalive 512;\n    }\n\n    server {\n        listen 80;\n\n        location / {\n            proxy_pass http://backend;\n            proxy_http_version 1.1;\n            proxy_set_header Connection &quot;&quot;;\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Proto $scheme;\n        }\n    }\n}\n</code></pre>\n<h2>Performance Results - After Tuning</h2>\n<p>Here are the results after re-running the tests:</p>\n<pre><code>| VUs  | YARP RPS | NGINX RPS | YARP p90 Latency (ms) | NGINX p90 Latency (ms) | YARP p95 Latency (ms) | NGINX p95 Latency (ms) |\n|------|----------|-----------|-----------------------|------------------------|-----------------------|------------------------|\n| 10   | 12692    | 17572     | 1.04                  | 0.58                   | 1.06                  | 0.74                   |\n| 50   | 27080    | 36687     | 2.70                  | 1.81                   | 3.18                  | 2.09                   |\n| 100  | 32432    | 43289     | 4.66                  | 3.18                   | 5.43                  | 3.88                   |\n| 200  | 36662    | 46850     | 7.77                  | 6.34                   | 8.81                  | 7.72                   |\n</code></pre>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/rps_comparison_tuned.png\" alt=\"YARP vs Nginx RPS comparison\">\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/p90_comparison_tuned.png\" alt=\"YARP vs Nginx p90 latency comparison\">\n<img src=\"https://milanjovanovic.tech/blogs/mnw_144/p95_comparison_tuned.png\" alt=\"YARP vs Nginx p95 latency comparison\">\n<h3>Throughput Analysis</h3>\n<p>Now this is more interesting.\nNginx actually edges out YARP in raw throughput - hitting 46k requests per second vs YARP's 36k at 200 users.\nBoth proxies scale well as load increases, but Nginx shows why it's been the go-to choice for high-traffic sites.</p>\n<h3>Latency Comparison</h3>\n<p>The latency story is pretty close.\nAt lower loads, Nginx actually has better response times.\nAt 200 users, both proxies keep response times reasonable - YARP at 7.77ms and Nginx at 6.34ms for p90 latency.\nThe difference isn't huge either way.</p>\n<h2>Key Takeaways</h2>\n<p><strong>Configuration matters more than you think</strong>.\nThe initial results showed YARP crushing Nginx, but that was with Nginx's conservative defaults.\nOnce properly tuned, Nginx shows why it's been powering the internet for years.</p>\n<p><strong>Nginx wins on raw performance</strong>.\nWith proper configuration, Nginx handles more requests and keeps latency slightly lower.\nThat extra throughput matters when you're dealing with serious traffic.</p>\n<p><strong>YARP offers better integration</strong>.\nEven though Nginx edges out performance, YARP feels natural in .NET projects.\nSame configuration style, same patterns, same tooling.\nSometimes that developer experience is worth more than a few extra requests per second.</p>\n<p><strong>Always tune your tools</strong>.\nThis whole exercise shows why benchmarks with default configs can be misleading.\nIf you're choosing between these two, make sure you're comparing optimized configurations, not defaults.</p>\n<p>The choice isn't as clear-cut as I initially thought.\nNginx wins on pure performance, but YARP wins on .NET integration.\nPick based on what matters more for your specific situation.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/yarp-vs-nginx-a-quick-performance-comparison",
            "title": "YARP vs Nginx - A Quick Performance Comparison",
            "summary": "In this article, we will compare the performance of YARP and Nginx, two popular reverse proxy solutions.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_144.png",
            "date_modified": "2025-05-31T00:00:00.000Z",
            "date_published": "2025-05-31T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-a-custom-domain-events-dispatcher-in-dotnet",
            "content_html": "<p>Domain events let one part of your system publish an event and other parts subscribe to it, instead of calling email or analytics services directly.\nYou can build an in-process dispatcher in .NET with two small interfaces, DI registration, and a strongly typed handler wrapper, no third-party libraries required.</p>\n<p>Domain events are a powerful way to decouple parts of your system.\nInstead of tightly coupling your logic, you can publish events and have other parts of your code subscribe to those events.\nThis pattern is especially valuable in <a href=\"https://en.wikipedia.org/wiki/Domain-driven_design\">Domain-Driven Design</a> (DDD)\nwhere business logic should remain focused and cohesive.</p>\n<p>In this article, we'll walk through how to implement a lightweight, custom domain event dispatcher in .NET.\nThe core dispatching logic should not depend on third-party libraries.</p>\n<p>We'll cover:</p>\n<ul>\n<li>Why you might want to use publish-subscribe in your application</li>\n<li>How to define basic domain event abstractions</li>\n<li>How to implement and register handlers</li>\n<li>How to build a domain events dispatcher</li>\n<li>Trade-offs and when to consider other options</li>\n</ul>\n<p>Let's get started.</p>\n<h2>Why Domain Events Matter</h2>\n<p>Before diving into implementation, let's understand the problem domain events solve.\nConsider this tightly coupled code:</p>\n<pre><code class=\"language-csharp\">public class UserService\n{\n    public async Task RegisterUser(string email, string password)\n    {\n        var user = new User(email, password);\n        await _userRepository.SaveAsync(user);\n\n        // Directly coupled to email service\n        await _emailService.SendWelcomeEmail(user.Email);\n\n        // Directly coupled to analytics\n        await _analyticsService.TrackUserRegistration(user.Id);\n\n        // What if we need to add more features?\n        // This method will keep growing...\n    }\n}\n</code></pre>\n<p>With domain events, we can decouple this:</p>\n<pre><code class=\"language-csharp\">public class UserService\n{\n    public async Task RegisterUser(string email, string password)\n    {\n        var user = new User(email, password);\n        await _userRepository.SaveAsync(user);\n\n        // Publish event - let other parts of the system react\n        await _domainEventsDispatcher.DispatchAsync(\n            [new UserRegisteredDomainEvent(user.Id, user.Email)]);\n    }\n}\n</code></pre>\n<p>Now the <code>UserService</code> focuses solely on user registration, while other concerns are handled through event handlers.</p>\n<h2>Basic Abstractions</h2>\n<p>Let's start by defining two simple interfaces that form the foundation of our event system:</p>\n<pre><code class=\"language-csharp\">// Marker interface for all domain events.\npublic interface IDomainEvent\n{\n    // We could add common properties here like:\n    // DateTime OccurredAt { get; }\n    // Guid EventId { get; }\n}\n\n// Generic interface for handling domain events.\npublic interface IDomainEventHandler&lt;in T&gt; where T : IDomainEvent\n{\n    Task Handle(T domainEvent, CancellationToken cancellationToken = default);\n}\n</code></pre>\n<p>This design gives us type safety through generic constraints while keeping publishers and handlers completely decoupled.\nYou can add new events or handlers without touching existing code, and everything remains easily testable in isolation.</p>\n<h2>Implementing Sample Handlers</h2>\n<p>Let's add some sample handlers that demonstrate how different parts of your system can react to the same event:</p>\n<pre><code class=\"language-csharp\">// Handles sending welcome emails when users register\ninternal sealed class SendWelcomeEmailHandler(IEmailService emailService)\n    : IDomainEventHandler&lt;UserRegisteredDomainEvent&gt;\n{\n    public async Task Handle(\n        UserRegisteredDomainEvent domainEvent,\n        CancellationToken cancellationToken = default)\n    {\n        // Send welcome email\n        var welcomeEmail = new WelcomeEmail(domainEvent.Email, domainEvent.UserId);\n\n        await emailService.SendAsync(welcomeEmail, cancellationToken);\n    }\n}\n\n// Handles analytics tracking for new user registrations\ninternal sealed class TrackUserRegistrationHandler(IAnalyticsService analyticsService)\n    : IDomainEventHandler&lt;UserRegisteredDomainEvent&gt;\n{\n    public async Task Handle(\n        UserRegisteredDomainEvent domainEvent,\n        CancellationToken cancellationToken = default)\n    {\n        // Track registration in analytics\n        await analyticsService.TrackEvent(\n            &quot;user_registered&quot;,\n            new\n            {\n                user_id = domainEvent.UserId,\n                registration_date = domainEvent.RegisteredAt\n            },\n            cancellationToken);\n    }\n}\n</code></pre>\n<p>To make this work, we need to register our handlers with the DI container.</p>\n<p>Here's how to do it manually:</p>\n<pre><code class=\"language-csharp\">// In your Program.cs or Startup.cs\nservices.AddScoped&lt;IDomainEventHandler&lt;UserRegisteredDomainEvent&gt;, SendWelcomeEmailHandler&gt;();\nservices.AddScoped&lt;IDomainEventHandler&lt;UserRegisteredDomainEvent&gt;, TrackUserRegistrationHandler&gt;();\n</code></pre>\n<p>Or you can automate this registration using <a href=\"https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor\"><strong>assembly scanning with Scrutor</strong></a>:</p>\n<pre><code class=\"language-csharp\">services.Scan(scan =&gt; scan.FromAssembliesOf(typeof(DependencyInjection))\n    .AddClasses(classes =&gt; classes.AssignableTo(typeof(IDomainEventHandler&lt;&gt;)), publicOnly: false)\n    .AsImplementedInterfaces()\n    .WithScopedLifetime());\n</code></pre>\n<p>The important thing is that multiple handlers can react to the same event.</p>\n<h2>The Dispatcher (Strongly Typed)</h2>\n<p>Now we need something to orchestrate calling the handlers.\nThe dispatcher will take the domain events and call the appropriate handlers for each event.</p>\n<pre><code class=\"language-csharp\">public interface IDomainEventsDispatcher\n{\n    Task DispatchAsync(\n        IEnumerable&lt;IDomainEvent&gt; domainEvents,\n        CancellationToken cancellationToken = default);\n}\n\ninternal sealed class DomainEventsDispatcher(IServiceProvider serviceProvider)\n    : IDomainEventsDispatcher\n{\n    private static readonly ConcurrentDictionary&lt;Type, Type&gt; HandlerTypeDictionary = new();\n    private static readonly ConcurrentDictionary&lt;Type, Type&gt; WrapperTypeDictionary = new();\n\n    public async Task DispatchAsync(\n        IEnumerable&lt;IDomainEvent&gt; domainEvents,\n        CancellationToken cancellationToken = default)\n    {\n        foreach (IDomainEvent domainEvent in domainEvents)\n        {\n            using IServiceScope scope = serviceProvider.CreateScope();\n\n            Type domainEventType = domainEvent.GetType();\n\n            Type handlerType = HandlerTypeDictionary.GetOrAdd(\n                domainEventType,\n                et =&gt; typeof(IDomainEventHandler&lt;&gt;).MakeGenericType(et));\n\n            IEnumerable&lt;object?&gt; handlers = scope.ServiceProvider.GetServices(handlerType);\n\n            foreach (object? handler in handlers)\n            {\n                if (handler is null) continue;\n\n                var handlerWrapper = HandlerWrapper.Create(handler, domainEventType);\n\n                await handlerWrapper.Handle(domainEvent, cancellationToken);\n            }\n        }\n    }\n\n    // Abstract base class for strongly-typed handler wrappers\n    private abstract class HandlerWrapper\n    {\n        public abstract Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken);\n\n        public static HandlerWrapper Create(object handler, Type domainEventType)\n        {\n            Type wrapperType = WrapperTypeDictionary.GetOrAdd(\n                domainEventType,\n                et =&gt; typeof(HandlerWrapper&lt;&gt;).MakeGenericType(et));\n\n            return (HandlerWrapper)Activator.CreateInstance(wrapperType, handler)!;\n        }\n    }\n\n    // Generic wrapper that provides strong typing for handler invocation\n    private sealed class HandlerWrapper&lt;T&gt;(object handler) : HandlerWrapper where T : IDomainEvent\n    {\n        private readonly IDomainEventHandler&lt;T&gt; _handler = (IDomainEventHandler&lt;T&gt;)handler;\n\n        public override async Task Handle(\n            IDomainEvent domainEvent,\n            CancellationToken cancellationToken)\n        {\n            await _handler.Handle((T)domainEvent, cancellationToken);\n        }\n    }\n}\n</code></pre>\n<p>The dispatcher uses a wrapper to eliminate reflection during handler execution while maintaining type safety.\nWhen we encounter a <code>UserRegisteredDomainEvent</code>, we create a <code>HandlerWrapper&lt;UserRegisteredDomainEvent&gt;</code>\nthat holds a strongly-typed reference to <code>IDomainEventHandler&lt;UserRegisteredDomainEvent&gt;</code>.\nThe wrapper casts the generic <code>IDomainEvent</code> to the specific event type at runtime, but the actual handler invocation uses compile-time types.</p>\n<p>This gives us the performance benefits of avoiding reflection in the hot path (handler execution) while only using reflection once during wrapper creation.\nThe trade-off is additional complexity, but the performance gain is significant if you're dispatching many events.</p>\n<p>Don't forget to register the dispatcher with DI:</p>\n<pre><code class=\"language-csharp\">services.AddTransient&lt;IDomainEventsDispatcher, DomainEventsDispatcher&gt;();\n</code></pre>\n<h2>Usage Example</h2>\n<p>Here's how to use the domain events dispatcher in your application:</p>\n<pre><code class=\"language-csharp\">public class UserController(\n    IUserService userService,\n    IDomainEventsDispatcher domainEventsDispatcher) : ControllerBase\n{\n    [HttpPost(&quot;register&quot;)]\n    public async Task&lt;IActionResult&gt; Register([FromBody] RegisterUserRequest request)\n    {\n        try\n        {\n            // Create the user\n            var user = await userService.CreateUserAsync(request.Email, request.Password);\n\n            // Publish the domain event\n            var userRegisteredEvent = new UserRegisteredDomainEvent(user.Id, user.Email);\n\n            await domainEventsDispatcher.DispatchAsync([userRegisteredEvent]);\n\n            return Ok(new { UserId = user.Id, Message = &quot;User registered successfully&quot; });\n        }\n        catch (Exception ex)\n        {\n            return BadRequest(new { Error = ex.Message });\n        }\n    }\n}\n</code></pre>\n<p>You could also <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>integrate domain events directly into your domain entities</strong></a>.</p>\n<h2>Limitations and Tradeoffs</h2>\n<p>This implementation runs entirely in-process, which has important implications.\nAll handlers execute synchronously within the same request context, but each gets its own DI scope.\nThis means:</p>\n<ul>\n<li>\n<p><strong>Immediate feedback</strong>:\nIf any handler fails, the exception bubbles up to the caller immediately.\nNo silent failures or <strong>eventual consistency</strong> surprises.</p>\n</li>\n<li>\n<p><strong>Caller control</strong>:\nThe code that dispatches events decides how to handle failures — rollback transactions, retry operations, or continue despite errors.\nThe dispatcher doesn't make these decisions for you.</p>\n</li>\n<li>\n<p><strong>Reliability concerns</strong>:\nIf the process crashes after some handlers succeed but before others complete, there's no automatic recovery.\nEvents aren't persisted or retried.</p>\n</li>\n</ul>\n<p>For critical side effects that can't be lost, consider the <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\"><strong>Outbox pattern</strong></a>.\nInstead of dispatching events immediately, store them alongside your business data in the same transaction.\nA background service can later retry failed events, ensuring nothing gets lost.\nThis decouples reliability from performance — your main operation completes quickly while events are processed reliably in the background.</p>\n<h2>Wrapping Up</h2>\n<p>Domain events are a powerful pattern for decoupling business logic, and you don't need a heavyweight framework to use them effectively.\nThe implementation we've built here provides a solid foundation that you can extend as your needs grow.</p>\n<p>The beauty of rolling your own solution is that you understand every piece, making debugging and customization straightforward.\nThis pattern fits excellently in <a href=\"https://milanjovanovic.tech/pragmatic-domain-driven-design\"><strong>Domain-Driven Design</strong></a> and\n<a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture</strong></a> systems where decoupling business logic is crucial.</p>\n<p>For systems requiring bulletproof reliability or cross-service communication, invest in proper <strong>message infrastructure</strong>.\nBut for many applications, this simple approach hits the sweet spot between coupling and complexity.</p>\n<p>The key insight is understanding your trade-offs upfront rather than discovering them in production.\nStart simple, measure what matters, and evolve based on real requirements.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-a-custom-domain-events-dispatcher-in-dotnet",
            "title": "Building a Custom Domain Events Dispatcher in .NET",
            "summary": "Learn how to build a lightweight, in-process domain events dispatcher in .NET without external dependencies.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_143.png",
            "date_modified": "2025-05-24T00:00:00.000Z",
            "date_published": "2025-05-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/cqrs-pattern-the-way-it-should-have-been-from-the-start",
            "content_html": "<p>You don't need MediatR to implement CQRS in .NET.\nA few simple abstractions cover what most projects use it for: <code>ICommand</code>, <code>IQuery</code>, their handler interfaces, and decorators for cross-cutting concerns.\nIn this article, I'll show you how to build that lightweight setup with plain interfaces and DI.</p>\n<p>MediatR is going commercial.</p>\n<p><a href=\"https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/\">Jimmy Bogard recently announced</a>\nthat MediatR will adopt a commercial license model for companies above a certain size.</p>\n<p>For many teams, this is a trigger to re-evaluate their usage and possibly look for alternatives.</p>\n<p>And it's not a bad time to do so.\nMediatR became almost synonymous with CQRS in .NET, despite the fact that <a href=\"https://milanjovanovic.tech/blog/stop-conflating-cqrs-and-mediatr\"><strong>CQRS and MediatR are not the same thing</strong></a>.\nMost projects use it as a thin dispatching layer for commands and queries — a use case that can be covered with a few straightforward abstractions.</p>\n<p>By removing MediatR, you gain:</p>\n<ul>\n<li>Full control over your CQRS infrastructure</li>\n<li>Predictable, explicit handler dispatching</li>\n<li>Simpler debugging and onboarding</li>\n<li>Cleaner DI setup and better testability</li>\n</ul>\n<p>In this article, I'll walk you through building a minimal CQRS setup with just a few interfaces and support for decorators.\nNo hidden DI magic.\nJust clean, predictable code.</p>\n<p>We'll cover:</p>\n<ul>\n<li>Defining <code>ICommand</code>, <code>IQuery</code>, and handler contracts</li>\n<li>Adding support for decorators (logging, validation, etc.)</li>\n<li>Registering everything with DI</li>\n<li>A full working example in a real-world scenario</li>\n</ul>\n<p>Let's get started.</p>\n<h2>Commands, Queries, and Handlers</h2>\n<p>Let's start by defining the basic contracts for commands and queries.</p>\n<pre><code class=\"language-csharp\">// ICommand.cs\npublic interface ICommand;\npublic interface ICommand&lt;TResponse&gt;;\n\n// IQuery.cs\npublic interface IQuery&lt;TResponse&gt;;\n</code></pre>\n<p>These interfaces exist purely as markers.\nThey allow us to structure application logic around intention — write operations go through <code>ICommand</code>, read operations through <code>IQuery</code>.</p>\n<p>The handler interfaces follow the same model:</p>\n<pre><code class=\"language-csharp\">// ICommandHandler.cs\npublic interface ICommandHandler&lt;in TCommand&gt;\n    where TCommand : ICommand\n{\n    Task&lt;Result&gt; Handle(TCommand command, CancellationToken cancellationToken);\n}\n\npublic interface ICommandHandler&lt;in TCommand, TResponse&gt;\n    where TCommand : ICommand&lt;TResponse&gt;\n{\n    Task&lt;Result&lt;TResponse&gt;&gt; Handle(TCommand command, CancellationToken cancellationToken);\n}\n</code></pre>\n<pre><code class=\"language-csharp\">// IQueryHandler.cs\npublic interface IQueryHandler&lt;in TQuery, TResponse&gt;\n    where TQuery : IQuery&lt;TResponse&gt;\n{\n    Task&lt;Result&lt;TResponse&gt;&gt; Handle(TQuery query, CancellationToken cancellationToken);\n}\n</code></pre>\n<p>These are nearly identical to MediatR's <code>IRequest</code> and <code>IRequestHandler</code> APIs, making migration trivial if you're moving off of MediatR.</p>\n<p>You'll notice we're using a <code>Result</code> wrapper for all return types.\nThis is optional, but it promotes explicit success/failure handling and encourages consistency across the application boundary.\nYou can learn more about it in my <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>previous article</strong></a>.</p>\n<p>These interfaces form a lightweight CQRS infrastructure, focused purely on intent and separation of concerns.\nNo mediator, no runtime indirection — just clear contracts for handling reads and writes.</p>\n<h2>Practical Example: Command Handler</h2>\n<p>To see these abstractions in action, let's implement a command that marks a todo item as completed.</p>\n<pre><code class=\"language-csharp\">// CompleteTodoCommand.cs\npublic sealed record CompleteTodoCommand(Guid TodoItemId) : ICommand;\n\n// CompleteTodoCommandHandler.cs\ninternal sealed class CompleteTodoCommandHandler(\n    IApplicationDbContext context,\n    IDateTimeProvider dateTimeProvider,\n    IUserContext userContext)\n    : ICommandHandler&lt;CompleteTodoCommand&gt;\n{\n    public async Task&lt;Result&gt; Handle(CompleteTodoCommand command, CancellationToken cancellationToken)\n    {\n        TodoItem? todoItem = await context.TodoItems\n            .SingleOrDefaultAsync(\n                t =&gt; t.Id == command.TodoItemId &amp;&amp; t.UserId == userContext.UserId,\n                cancellationToken);\n\n        if (todoItem is null)\n        {\n            return Result.Failure(TodoItemErrors.NotFound(command.TodoItemId));\n        }\n\n        if (todoItem.IsCompleted)\n        {\n            return Result.Failure(TodoItemErrors.AlreadyCompleted(command.TodoItemId));\n        }\n\n        todoItem.IsCompleted = true;\n        todoItem.CompletedAt = dateTimeProvider.UtcNow;\n\n        todoItem.Raise(new TodoItemCompletedDomainEvent(todoItem.Id));\n\n        await context.SaveChangesAsync(cancellationToken);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>A few important things to note:</p>\n<ul>\n<li>The command is an immutable value object (just data, no behavior).</li>\n<li>The handler encapsulates all business logic: validation, state change, raising domain events, and persistence.</li>\n<li>There's no mediator, no <code>ISender</code>, no hidden dispatching. The handler is invoked directly via our custom abstractions.</li>\n</ul>\n<p>This makes intent explicit, avoids magic, and keeps the dependencies minimal.</p>\n<p>We'll look at how to add decorators next, so we can introduce things like logging, validation, or transactions without modifying the handler itself.</p>\n<h2>Decorators</h2>\n<p>To support cross-cutting concerns like logging, validation, and transactions, we apply the <strong>decorator pattern</strong> around our handlers.\nTechnically, this is closer to the <strong>proxy pattern</strong>, since we're injecting behavior before/after delegating to the real handler.\nBut in the context of cross-cutting concerns, most people refer to this as a decorator — which is fine for our purposes.</p>\n<p>Let's look at two examples: one for logging, one for validation.</p>\n<pre><code class=\"language-csharp\">using Serilog.Context;\n\ninternal sealed class LoggingCommandHandler&lt;TCommand, TResponse&gt;(\n    ICommandHandler&lt;TCommand, TResponse&gt; innerHandler,\n    ILogger&lt;CommandHandler&lt;TCommand, TResponse&gt;&gt; logger)\n    : ICommandHandler&lt;TCommand, TResponse&gt;\n    where TCommand : ICommand&lt;TResponse&gt;\n{\n    public async Task&lt;Result&lt;TResponse&gt;&gt; Handle(TCommand command, CancellationToken cancellationToken)\n    {\n        string commandName = typeof(TCommand).Name;\n\n        logger.LogInformation(&quot;Processing command {Command}&quot;, commandName);\n\n        Result&lt;TResponse&gt; result = await innerHandler.Handle(command, cancellationToken);\n\n        if (result.IsSuccess)\n        {\n            logger.LogInformation(&quot;Completed command {Command}&quot;, commandName);\n        }\n        else\n        {\n            using (LogContext.PushProperty(&quot;Error&quot;, result.Error, true))\n            {\n                logger.LogError(&quot;Completed command {Command} with error&quot;, commandName);\n            }\n        }\n\n        return result;\n    }\n}\n</code></pre>\n<p>This class wraps any <code>ICommandHandler&lt;TCommand, TResponse&gt;</code>, injecting the decorated handler as <code>innerHandler</code>.\nIt adds structured logging around the command execution without touching the core business logic.</p>\n<p>Now a <a href=\"https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation\"><strong>validation example with FluentValidation</strong></a>:</p>\n<pre><code class=\"language-csharp\">using FluentValidation;\nusing FluentValidation.Results;\n\ninternal sealed class ValidationCommandHandler&lt;TCommand, TResponse&gt;(\n    ICommandHandler&lt;TCommand, TResponse&gt; innerHandler,\n    IEnumerable&lt;IValidator&lt;TCommand&gt;&gt; validators)\n    : ICommandHandler&lt;TCommand, TResponse&gt;\n    where TCommand : ICommand&lt;TResponse&gt;\n{\n    public async Task&lt;Result&lt;TResponse&gt;&gt; Handle(TCommand command, CancellationToken cancellationToken)\n    {\n        // Validate the command using all registered validators\n        ValidationFailure[] validationFailures = await ValidateAsync(command, validators);\n\n        if (validationFailures.Length == 0)\n        {\n            return await innerHandler.Handle(command, cancellationToken);\n        }\n\n        // If validation fails, return a failure result with the errors\n        return Result.Failure&lt;TResponse&gt;(CreateValidationError(validationFailures));\n    }\n\n    private static async Task&lt;ValidationFailure[]&gt; ValidateAsync&lt;TCommand&gt;(\n        TCommand command,\n        IEnumerable&lt;IValidator&lt;TCommand&gt;&gt; validators)\n    {\n        if (!validators.Any())\n        {\n            return [];\n        }\n\n        var context = new ValidationContext&lt;TCommand&gt;(command);\n\n        ValidationResult[] validationResults = await Task.WhenAll(\n            validators.Select(validator =&gt; validator.ValidateAsync(context)));\n\n        ValidationFailure[] validationFailures = validationResults\n            .Where(validationResult =&gt; !validationResult.IsValid)\n            .SelectMany(validationResult =&gt; validationResult.Errors)\n            .ToArray();\n\n        return validationFailures;\n    }\n\n    private static ValidationError CreateValidationError(ValidationFailure[] validationFailures) =&gt;\n        new(validationFailures.Select(f =&gt; Error.Problem(f.ErrorCode, f.ErrorMessage)).ToArray());\n}\n</code></pre>\n<p>Each decorator handles a single concern and can be layered transparently around the core handler.</p>\n<p><strong>Important:</strong> Since we're working with generic interfaces (<code>ICommandHandler&lt;,&gt;</code>, <code>IQueryHandler&lt;,&gt;</code>),\neach decorator must explicitly target the same generic contract.\nThat means you'll need separate decorator classes for each handler abstraction you're using (e.g. command with result, command without result, query with result).</p>\n<p>In the next section, we'll wire this up using <a href=\"https://github.com/khellang/Scrutor\">Scrutor</a>.\nIt's a simple assembly scanning library that helps us register and decorate handlers cleanly.\nYes, it uses reflection, but only during startup — and it's fully transparent and predictable.</p>\n<h2>DI Setup</h2>\n<p>With our handlers and decorators in place, we can register everything using Scrutor.</p>\n<pre><code class=\"language-csharp\">services.Scan(scan =&gt; scan.FromAssembliesOf(typeof(DependencyInjection))\n    .AddClasses(classes =&gt; classes.AssignableTo(typeof(IQueryHandler&lt;,&gt;)), publicOnly: false)\n        .AsImplementedInterfaces()\n        .WithScopedLifetime()\n    .AddClasses(classes =&gt; classes.AssignableTo(typeof(ICommandHandler&lt;&gt;)), publicOnly: false)\n        .AsImplementedInterfaces()\n        .WithScopedLifetime()\n    .AddClasses(classes =&gt; classes.AssignableTo(typeof(ICommandHandler&lt;,&gt;)), publicOnly: false)\n        .AsImplementedInterfaces()\n        .WithScopedLifetime());\n</code></pre>\n<p>This scans the application assembly and registers all command and query handlers (including internal types) as their respective interfaces.</p>\n<p>Next, we apply decorators for validation and logging:</p>\n<pre><code class=\"language-csharp\">services.Decorate(typeof(ICommandHandler&lt;,&gt;), typeof(ValidationDecorator.CommandHandler&lt;,&gt;));\nservices.Decorate(typeof(ICommandHandler&lt;&gt;), typeof(ValidationDecorator.CommandBaseHandler&lt;&gt;));\n\nservices.Decorate(typeof(IQueryHandler&lt;,&gt;), typeof(LoggingDecorator.QueryHandler&lt;,&gt;));\nservices.Decorate(typeof(ICommandHandler&lt;,&gt;), typeof(LoggingDecorator.CommandHandler&lt;,&gt;));\nservices.Decorate(typeof(ICommandHandler&lt;&gt;), typeof(LoggingDecorator.CommandBaseHandler&lt;&gt;));\n</code></pre>\n<p>Each <code>Decorate</code> call wraps the previous registration.\n<strong>Order matters</strong>, but it might not be intuitive at first glance.</p>\n<p>The last decorator applied will be the outermost one at runtime.\nSo in this example:</p>\n<ul>\n<li>The <strong>base handler</strong> is first decorated by <strong>validation</strong></li>\n<li>That composite is then decorated again by <strong>logging</strong></li>\n</ul>\n<p>Which means the <strong>logging decorator runs first</strong>, followed by <strong>validation</strong>, and then the core handler.</p>\n<p>This order allows logging to capture the full command lifecycle, including any early exits from validation failures.</p>\n<p>With this setup, you now have a fully functional and extensible CQRS pipeline:</p>\n<ul>\n<li>Custom handler interfaces</li>\n<li>Clean decorator chain</li>\n<li>Assembly-scanned DI setup</li>\n</ul>\n<h2>Usage from Minimal API</h2>\n<p>Once everything is wired up, using a command handler from a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint is straightforward:</p>\n<pre><code class=\"language-csharp\">internal sealed class Complete : IEndpoint\n{\n    public void MapEndpoint(IEndpointRouteBuilder app)\n    {\n        app.MapPut(&quot;todos/{id:guid}/complete&quot;, async (\n            Guid id,\n            ICommandHandler&lt;CompleteTodoCommand&gt; handler,\n            CancellationToken cancellationToken) =&gt;\n        {\n            var command = new CompleteTodoCommand(id);\n\n            Result result = await handler.Handle(command, cancellationToken);\n\n            return result.Match(Results.NoContent, CustomResults.Problem);\n        })\n        .WithTags(Tags.Todos)\n        .RequireAuthorization();\n    }\n}\n</code></pre>\n<p>We're injecting the appropriate <code>ICommandHandler&lt;CompleteTodoCommand&gt;</code> directly into the endpoint.\nNo need for <code>ISender</code>, no mediator layer, no runtime lookup.</p>\n<p>This keeps the endpoint clean and focused on its primary responsibility: handling HTTP requests.</p>\n<p>Everything is resolved explicitly by the container.\nThis makes the code <strong>easier to test</strong>, reason about, and trace while maintaining all the benefits of CQRS and separation of concerns.</p>\n<h2>Conclusion</h2>\n<p>CQRS doesn't require a complex framework.</p>\n<p>With a few small interfaces, some decorator classes, and a clean DI setup, you can build a simple and flexible pipeline for handling commands and queries.\nIt's easy to understand, easy to test, and easy to extend.</p>\n<p>If you want to see this pattern applied in a complete solution,\nmy <a href=\"https://milanjovanovic.tech/templates/clean-architecture\"><strong>free Clean Architecture template</strong></a> includes everything covered in this article (fully wired up).</p>\n<p>Use it as a reference or as a starting point for your next project.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/cqrs-pattern-the-way-it-should-have-been-from-the-start",
            "title": "CQRS Pattern the Way It Should've Been From the Start",
            "summary": "Learn how to implement CQRS in .NET without relying on MediatR. This guide walks you through a lightweight setup using simple interfaces, decorators, and DI —…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_142.png",
            "date_modified": "2025-05-17T00:00:00.000Z",
            "date_published": "2025-05-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/from-anemic-models-to-behavior-driven-models-a-practical-ddd-refactor-in-csharp",
            "content_html": "<p>An anemic domain model keeps entities as data holders and pushes every rule into a service class.\nYou fix it by moving one rule at a time into the aggregate: a static <code>Create</code> factory that fails fast on broken invariants, and internal state that consumers cannot mutate.\nThe application layer is left with orchestration.</p>\n<p>If you've ever worked with a legacy C# codebase, you know the pain of an anemic domain model.\nYou have probably opened an <code>OrderService</code> (<em>all similarities to production code are merely a coincidence</em>) and thought <em>&quot;this file does everything.&quot;</em>\nPricing logic, discount rules, stock checks, database writes — <strong>all jam-packed into one class</strong>.\nIt works — until it doesn't.\nNew features turn into <strong>regression roulette</strong>, and test coverage plummets because the domain is buried under infrastructure.</p>\n<p>This is the classic symptom of an anemic domain model, where entities are nothing but data holders, and all logic lives elsewhere.\nIt makes the system harder to reason about, and every change becomes a guessing game.\nBut what if we could push behavior back into the domain, one rule at a time?</p>\n<p>In this article, we'll:</p>\n<ol>\n<li><strong>Inspect</strong> a typical anemic implementation.</li>\n<li><strong>Identify</strong> hidden business rules that make it brittle.</li>\n<li><strong>Refactor</strong> toward a behavior-rich aggregate one refactor at a time.</li>\n<li><strong>Highlight</strong> the concrete payoffs so you can justify the change to teammates.</li>\n</ol>\n<p>Everything fits in a 6-minute read, but the pattern scales to any legacy system.</p>\n<h2>Starting Point: God-like Service Class</h2>\n<p>Below is an (unfortunately common) <code>OrderService</code>.\nBesides calculating totals it also:</p>\n<ul>\n<li>applies a <strong>5 % VIP discount</strong>,</li>\n<li>throws if any product is <strong>out of stock</strong>, and</li>\n<li>rejects orders that would <strong>exceed the customer's credit limit</strong>.</li>\n</ul>\n<pre><code class=\"language-csharp\">// OrderService.cs\npublic void PlaceOrder(Guid customerId, IEnumerable&lt;OrderItemDto&gt; items)\n{\n    var customer = _db.Customers.Find(customerId);\n    if (customer is null)\n    {\n        throw new ArgumentException(&quot;Customer not found&quot;);\n    }\n\n    var order = new Order { CustomerId = customerId };\n\n    foreach (var dto in items)\n    {\n        var inventory = _inventoryService.GetStock(dto.ProductId);\n        if (inventory &lt; dto.Quantity)\n        {\n            throw new InvalidOperationException(&quot;Item out of stock&quot;);\n        }\n\n        var price = _pricingService.GetPrice(dto.ProductId);\n        var lineTotal = price * dto.Quantity;\n        if (customer.IsVip)\n        {\n            lineTotal *= 0.95m; // 5% discount for VIPs\n        }\n\n        order.Items.Add(new OrderItem\n        {\n            ProductId = dto.ProductId,\n            Quantity = dto.Quantity,\n            UnitPrice = price,\n            LineTotal = lineTotal\n        });\n    }\n\n    order.Total = order.Items.Sum(i =&gt; i.LineTotal);\n\n    if (customer.CreditUsed + order.Total &gt; customer.CreditLimit)\n    {\n        throw new InvalidOperationException(&quot;Credit limit exceeded&quot;);\n    }\n\n    _db.Orders.Add(order);\n    _db.SaveChanges();\n}\n</code></pre>\n<h3>What's Wrong Here?</h3>\n<ul>\n<li><strong>Scattered rules:</strong> Discount application, stock validation, and credit-limit checks are buried inside the service.</li>\n<li><strong>Tight coupling:</strong> <code>OrderService</code> must know about pricing, inventory, and EF Core just to place an order.</li>\n<li><strong>Painful testing:</strong> Each unit test needs fakes for DB access, pricing, inventory, and VIP vs. non-VIP flows.</li>\n</ul>\n<div className='blockquote'>\n<p><strong>Goal:</strong> Embed these rules <strong>inside the domain</strong> so the application layer only deals with orchestration.</p>\n</div>\n<h2>Guiding Principles Before We Touch Code</h2>\n<ol>\n<li><strong>Protect invariants close to the data.</strong>\nStock, discounts, and credit checks belong where the data lives — inside the <code>Order</code> aggregate.</li>\n<li><strong>Expose intent, hide mechanics.</strong>\nThe <a href=\"https://milanjovanovic.tech/blog/application-layer-clean-architecture\"><strong>application layer</strong></a> should read like a story: <em>&quot;place order&quot;</em>, not <em>&quot;calculate totals, check credit, write to DB&quot;</em>.</li>\n<li><strong>Refactor in slices.</strong>\nEach move is safe and compilable; no big-bang rewrites.</li>\n<li><strong>Balance purity with pragmatism.</strong>\nMove rules only when the payoff (clarity, safety, testability) beats the extra lines of code.</li>\n</ol>\n<h2>Step-by-Step Refactor</h2>\n<p>The goal here isn't to chase purity or academic DDD.\nIt's to incrementally improve cohesion and make room for the domain to express itself.</p>\n<p>At every step, we ask: Is this behavior something the domain should own?\nIf yes, we pull it inward.</p>\n<h3>Embed Creation &amp; Validation Logic</h3>\n<p>The first move is to make the aggregate responsible for building itself.\nA static <code>Create</code> method gives us a single entry point where all invariants can fail fast.</p>\n<p>While pushing stock validation into <code>Order</code> improves testability,\nit does couple the order flow with inventory availability.\nIn some domains, you'd instead model this as a domain event and validate asynchronously.</p>\n<pre><code class=\"language-csharp\">// Order.cs (Factory Method)\npublic static Order Create(\n    Customer customer,\n    IEnumerable&lt;(Guid productId, int quantity)&gt; lines,\n    IPricingService pricingService,\n    IInventoryService inventoryService)\n{\n    var order = new Order(customer.Id);\n\n    foreach (var (productId, quantity) in lines)\n    {\n        if (inventoryService.GetStock(productId) &lt; quantity)\n        {\n            throw new InvalidOperationException(&quot;Item out of stock&quot;);\n        }\n\n        var unitPrice = pricingService.GetPrice(productId);\n        order.AddItem(productId, quantity, unitPrice, customer.IsVip);\n    }\n\n    order.EnsureCreditWithinLimit(customer);\n\n    return order;\n}\n</code></pre>\n<p><strong>Why?</strong>\nCreation now <strong>fails fast</strong> if any invariant is broken.\nThe service no longer micromanages stock or discounts.</p>\n<p>Notice how we're now following the &quot;Tell, Don't Ask&quot; principle.\nRather than the service checking conditions and then manipulating the Order,\nwe're telling the Order to create itself with the necessary validations built in.\nThis is a fundamental shift toward <strong>encapsulation</strong>.</p>\n<p><strong>💡 On Injecting Services into Domain Methods</strong></p>\n<p>Passing services like <code>IPricingService</code> or <code>IInventoryService</code> into a domain method such as <code>Order.Create</code> might seem unconventional at first glance.\nBut it's a deliberate design choice: it keeps the orchestration inside the <a href=\"https://milanjovanovic.tech/blog/domain-layer-clean-architecture\"><strong>domain model</strong></a>, where the business logic naturally belongs,\ninstead of bloating the application service with procedural workflows.</p>\n<p>This approach maintains the entity's autonomy while still aligning with dependency injection principles — dependencies are passed explicitly, not resolved from within.\nIt's a powerful technique, but one that should be used selectively — only when the operation clearly fits within the domain's responsibility\nand benefits from direct access to external services.</p>\n<h3>Guard the Aggregate's Internal State</h3>\n<pre><code class=\"language-csharp\">// Order.cs (excerpt)\nprivate readonly List&lt;OrderItem&gt; _items = new();\npublic IReadOnlyCollection&lt;OrderItem&gt; Items =&gt; _items.AsReadOnly(); // C# 12 -&gt; [.._items]\n\nprivate void AddItem(Guid productId, int quantity, decimal unitPrice, bool isVip)\n{\n    if (quantity &lt;= 0)\n    {\n        throw new ArgumentException(&quot;Quantity must be positive&quot;);\n    }\n\n    var finalPrice = isVip ? unitPrice * 0.95m : unitPrice;\n    _items.Add(new OrderItem(productId, quantity, finalPrice));\n\n    RecalculateTotal();\n}\n\nprivate void EnsureCreditWithinLimit(Customer customer)\n{\n    if (customer.CreditUsed + Total &gt; customer.CreditLimit)\n    {\n        throw new InvalidOperationException(&quot;Credit limit exceeded&quot;);\n    }\n}\n</code></pre>\n<p><strong>Why bother?</strong></p>\n<ul>\n<li><strong>Encapsulation</strong>: Consumers can't mutate <code>_items</code> directly, ensuring invariants hold.</li>\n<li><strong>Self-protection</strong>: The domain model protects its own consistency rather than relying on service-level checks.</li>\n<li><strong>True OOP</strong>: Objects now combine data and behavior, as object-oriented programming intended.</li>\n<li><strong>Simpler services</strong>: Application services can focus on coordination rather than business rules.</li>\n</ul>\n<h3>Shrink the Application Layer to Pure Orchestration</h3>\n<pre><code class=\"language-csharp\">public void PlaceOrder(Guid customerId, IEnumerable&lt;OrderLineDto&gt; lines)\n{\n    var customer = _db.Customers.Find(customerId);\n    if (customer is null)\n    {\n        throw new ArgumentException(&quot;Customer not found&quot;);\n    }\n    var input = lines.Select(l =&gt; (l.ProductId, l.Quantity));\n\n    var order = Order.Create(customer, input, _pricingService, _inventoryService);\n\n    _db.Orders.Add(order);\n    _db.SaveChanges();\n}\n</code></pre>\n<p>The <code>PlaceOrder</code> method drops from <strong>44 lines</strong> to <strong>14</strong>, with <strong>zero business logic</strong>.</p>\n<h2>What We Gained</h2>\n<p><strong>Before the refactor</strong></p>\n<ul>\n<li>Service owned pricing, stock, discount, and credit checks.</li>\n<li>Unit tests required heavy EF Core and service fakes.</li>\n<li>Adding a new rule meant touching multiple files.</li>\n</ul>\n<p><strong>After the refactor</strong></p>\n<ul>\n<li>Aggregate owns all business rules; service only orchestrates.</li>\n<li>Pure domain tests — no database container required.</li>\n<li>Most changes are isolated to the <code>Order</code> aggregate.</li>\n</ul>\n<h2>Wrapping Up</h2>\n<p>The real value in refactoring anemic models isn't technical — it's strategic.</p>\n<p>By moving business logic closer to the data, you:</p>\n<ul>\n<li>Reduce the blast radius of changes</li>\n<li>Make business rules explicit and testable</li>\n<li>Open the door for tactical patterns like validation, events, and invariants</li>\n</ul>\n<p>But you don't need a big rewrite.\nStart with one rule.\nRefactor it.\nThen the next.</p>\n<p>That's how legacy systems evolve into maintainable architectures.</p>\n<p>If you enjoyed this breakdown and want a hands-on, real-world guide to untangling messy services,\ncheck out my course <a href=\"https://milanjovanovic.tech/pragmatic-domain-driven-design\"><strong>Pragmatic Domain-Driven Design</strong></a>.\nIt's packed with before-and-after examples like this one.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/from-anemic-models-to-behavior-driven-models-a-practical-ddd-refactor-in-csharp",
            "title": "From Anemic Models to Behavior-Driven Models: A Practical DDD Refactor in C#",
            "summary": "A practical guide to transforming anemic domain models into behavior-rich aggregates in C# through incremental refactoring, enhancing code maintainability and…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_141.png",
            "date_modified": "2025-05-10T00:00:00.000Z",
            "date_published": "2025-05-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq",
            "content_html": "<p>In .NET, an event-driven system with RabbitMQ has three parts: a producer that publishes to an exchange, a queue that stores the messages, and a consumer that reads from that queue.\nThe official <code>RabbitMQ.Client</code> package handles all three.\nCompeting consumers split the work, and a fanout exchange gives every consumer a copy.</p>\n<p><a href=\"https://en.wikipedia.org/wiki/Event-driven_architecture\">Event-driven architecture</a> (EDA) can make applications more flexible and reliable.\nInstead of one part of the system calling another directly, we let events flow through a message broker.\nIn this quick guide, I'll show you how to set up a simple event-driven system in .NET using <a href=\"https://www.rabbitmq.com/\">RabbitMQ</a>.</p>\n<p>We'll build a small example with a producer that sends events and a consumer that receives them.\nFor testing, I'll run RabbitMQ in a <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker container</strong></a> (with the Management UI enabled so we can see what's happening).\nWe'll use the official <a href=\"https://www.nuget.org/packages/rabbitmq.client/\">RabbitMQ.Client</a> NuGet package in a .NET console app.</p>\n<div className='blockquote'>\n<p>Note: If you don't have RabbitMQ installed, you can run it quickly with Docker. For example:</p>\n<pre><code class=\"language-bash\">docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-management\n</code></pre>\n<p>This starts a RabbitMQ broker on localhost (AMQP port <code>5672</code>) and a management website at <code>http://localhost:15672</code>.</p>\n</div>\n<h2>RabbitMQ Basics</h2>\n<p>Before coding, let's cover the basic components in RabbitMQ:</p>\n<ul>\n<li><strong>Producer</strong>: an application that sends messages (events) to RabbitMQ.</li>\n<li><strong>Consumer</strong>: an application that receives messages from a queue.</li>\n<li><strong>Queue</strong>: a mailbox inside RabbitMQ that stores messages.\nConsumers read from queues.\nMany producers can send to the same queue.</li>\n<li><strong>Exchange</strong>: a routing mechanism that receives messages from producers and directs them to queues.\nProducers actually send to an exchange instead of directly to a queue.\nThis decouples producers from specific queues - the exchange can decide where messages go, based on rules.</li>\n</ul>\n<p>In RabbitMQ, you can have multiple producers and multiple consumers.\nProducers never send directly to a queue by name; instead, they send to an exchange.\nThe exchange decides which queues (if any) should get each message based on routing rules.</p>\n<p>For now, we'll use a simple setup where the exchange will deliver all messages to one queue.</p>\n<h2>Producer - Sending Events</h2>\n<p>Let's start with the producer.\nIn our .NET console app, we'll use RabbitMQ.Client to connect to the RabbitMQ broker and send a message.</p>\n<p>For instance, an <code>OrderPlaced</code> event could trigger downstream services - inventory, email notifications, etc. -\nwithout the ordering system needing to call them directly.</p>\n<pre><code class=\"language-csharp\">var factory = new ConnectionFactory() { HostName = &quot;localhost&quot; };\nusing var connection = await factory.CreateConnectionAsync();\nusing var channel = await connection.CreateChannelAsync();\n\n// Ensure the queue exists (create it if not already there)\nawait channel.QueueDeclareAsync(\n    queue: &quot;orders&quot;,\n    durable: true, // save to disk so the queue isn't lost on broker restart\n    exclusive: false, // can be used by other connections\n    autoDelete: false, // don't delete when the last consumer disconnects\n    arguments: null);\n\n// Create a message\nvar orderPlaced = new OrderPlaced\n{\n     OrderId = Guid.NewGuid(),\n     Total = 99.99,\n     CreatedAt = DateTime.UtcNow\n};\nvar message = JsonSerializer.Serialize(orderPlaced);\nvar body = Encoding.UTF8.GetBytes(message);\n\n// Publish the message\nawait channel.BasicPublishAsync(\n    exchange: string.Empty, // default exchange\n    routingKey: &quot;orders&quot;,\n    mandatory: true, // fail if the message can't be routed\n    basicProperties: new BasicProperties { Persistent = true }, // message will be saved to disk\n    body: body);\n\nConsole.WriteLine($&quot;Sent: {message}&quot;);\n</code></pre>\n<p>This code connects to RabbitMQ on <code>localhost</code>, declares a queue named <code>orders</code> (creates it if it doesn't exist already),\nand publishes an <code>OrderPlaced</code> message to that queue.\nWe use an empty string for the exchange parameter, which tells RabbitMQ to use the default exchange.\nThe default exchange routes the message directly to the <code>orders</code> queue.</p>\n<p>What's happening here:</p>\n<ul>\n<li>We declare a <strong>durable queue</strong>, so it survives RabbitMQ restarts</li>\n<li>We mark the message as <strong>persistent</strong>, which tells RabbitMQ to write it to disk</li>\n<li>We serialize an object into JSON and send it as a UTF-8 encoded byte array</li>\n</ul>\n<p>Now let's look at the consumer side.</p>\n<h2>Consumer - Receiving Events</h2>\n<p>Next, let's set up a consumer to receive messages from the queue.\nThe consumer will also connect to RabbitMQ and subscribe to the same queue.</p>\n<p>To test this out, start the consumer application first (it will wait for messages), then run the producer application to send an event.</p>\n<pre><code class=\"language-csharp\">var factory = new ConnectionFactory() { HostName = &quot;localhost&quot; };\nusing var connection = await factory.CreateConnectionAsync();\nusing var channel = await connection.CreateChannelAsync();\n\n// Declare (or check) the queue to consume from\nawait channel.QueueDeclareAsync(\n    queue: &quot;orders&quot;,\n    durable: true, // must match the producer's queue settings\n    exclusive: false, // can be used by other connections\n    autoDelete: false, // don't delete when the last consumer disconnects\n    arguments: null);\n\n// Define a consumer and start listening\nvar consumer = new AsyncEventingBasicConsumer(channel);\nconsumer.ReceivedAsync += async (sender, eventArgs) =&gt;\n{\n    byte[] body = eventArgs.Body.ToArray();\n    string message = Encoding.UTF8.GetString(body);\n    var orderPlaced = JsonSerializer.Deserialize&lt;OrderPlaced&gt;(message);\n\n    Console.WriteLine($&quot;Received: OrderPlaced - {orderPlaced.OrderId}&quot;);\n\n    // Acknowledge the message\n    await ((AsyncEventingBasicConsumer)sender)\n        .Channel.BasicAckAsync(eventArgs.DeliveryTag, multiple: false);\n};\nawait channel.BasicConsumeAsync(&quot;orders&quot;, autoAck: false, consumer);\n\nConsole.WriteLine(&quot;Waiting for messages...&quot;);\n</code></pre>\n<p>The consumer code declares the same <code>orders</code> queue and sets up an event handler for incoming messages.\nWe call <code>BasicConsumeAsync</code> to start listening on the queue.\nRabbitMQ will push any new messages to our consumer's event handler.\nWhenever a message arrives, the <code>consumer.ReceivedAsync</code> event fires, and we print out the message.</p>\n<p>What's important here:</p>\n<ul>\n<li><code>autoAck: false</code> ensures we only acknowledge messages we actually process</li>\n<li>If processing fails, we could use <code>BasicNack</code> to requeue or route to a dead-letter queue</li>\n<li>Deserializing into a strongly typed object makes it easy to reason about the event</li>\n</ul>\n<p>So far we've had one consumer. But what if we run multiple consumers on the same queue?</p>\n<h2>Competing Consumers - Scaling Out</h2>\n<p>What if you have multiple consumers for the same queue?\nRabbitMQ allows <strong>competing consumers</strong> on a queue.</p>\n<p>If two or more consumers listen on the same queue, each message from that queue will be delivered to <strong>only one</strong> of them:</p>\n<ul>\n<li>RabbitMQ will distribute messages among the consumers (roughly in round-robin order)</li>\n<li>This is great for scaling: you can run multiple instances of a worker to process messages in parallel</li>\n</ul>\n<p>In other words, consumers <em>compete</em> for messages on that queue.\nThis pattern helps spread the workload, but note that each individual message is still processed by a single consumer.</p>\n<h2>Fanout Exchange: Broadcast to Multiple Consumers</h2>\n<p>Competing consumers share the work by dividing messages, but sometimes you want every service to get the event.\nThat's where a <strong>fanout exchange</strong> comes in.</p>\n<p>In RabbitMQ, a fanout exchange is used for broadcasting events to multiple consumers.\nInstead of all consumers sharing one queue, each consumer has its own queue.\nWhen the producer sends a message to a fanout exchange, the exchange copies and routes the message to all bound queues.\nThis way, every consumer receives a copy via its own queue.</p>\n<p>To set this up in code, we declare a fanout exchange and bind queues to it.</p>\n<p><strong>Producer</strong>:</p>\n<pre><code class=\"language-csharp\">// Producer setup for fanout\nawait channel.ExchangeDeclareAsync(\n    exchange: &quot;orders&quot;,\n    durable: true, // durable exchange\n    autoDelete: false, // don't delete when the last consumer disconnects\n    type: ExchangeType.Fanout);\n\n// Publish a message to the fanout exchange (routingKey is ignored for fanout)\nvar orderPlaced = new OrderPlaced\n{\n     OrderId = Guid.NewGuid(),\n     Total = 99.99,\n     CreatedAt = DateTime.UtcNow\n};\nvar message = JsonSerializer.Serialize(orderPlaced);\nvar body = Encoding.UTF8.GetBytes(message);\n\nawait channel.BasicPublishAsync(\n    exchange: &quot;orders&quot;,\n    routingKey: string.Empty,\n    mandatory: true,\n    basicProperties: new BasicProperties { Persistent = true },\n    body: body);\n</code></pre>\n<p><strong>Consumer</strong>:</p>\n<pre><code class=\"language-csharp\">// Consumer setup for fanout\nawait channel.ExchangeDeclareAsync(\n    exchange: &quot;orders&quot;,\n    durable: true,\n    autoDelete: false,\n    type: ExchangeType.Fanout);\n\n// Create a queue for this consumer and bind it\nawait channel.QueueDeclareAsync(\n    queue: &quot;orders-consumer-1&quot;,\n    durable: true,\n    exclusive: false,\n    autoDelete: false,\n    arguments: null);\n\nawait channel.QueueBindAsync(&quot;orders-consumer-1&quot;, &quot;orders&quot;, routingKey: string.Empty);\n\n// Then consume messages from queueName as usual...\n</code></pre>\n<p>In the producer, we call <code>ExchangeDeclareAsync</code> to make sure an <code>orders</code> exchange exists (of type fanout).\nWe then <code>BasicPublishAsync</code> to that exchange.\nFor a fanout exchange, the <code>routingKey</code> can be an empty string because it's ignored (fanout sends to all queues regardless of any routing key).\nOn the consumer side, we declare the same exchange and then create a new <code>orders-consumer-1</code> queue.\nWe bind that queue to the <code>orders</code> exchange.\nNow any message sent to the exchange will be delivered to this queue, and we can consume it.</p>\n<p>If you run multiple consumer programs (each with its own queue bound to <code>orders</code> exchange),\neach one will get every message (unlike the competing consumers scenario).\nYou can also peek into RabbitMQ's Management UI to see the exchange and queues in action.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_140/rabbitmq_ui.png\" alt=\"RabbitMQ Management UI\">\n</div>\n<h2>Next Steps</h2>\n<p>You can expand this basic setup with more advanced RabbitMQ features.\nFor example, you might use a <strong>direct exchange</strong> or <strong>topic exchange</strong> to route events to specific services,\nset up acknowledgment and retry policies for robustness,\nor implement <strong>dead-letter queues</strong> for error handling.\nThe core idea throughout is the same: decouple senders and receivers with a <a href=\"https://milanjovanovic.tech/blog/rabbitmq-vs-kafka-dotnet\"><strong>message broker</strong></a>, making your system more flexible and resilient.</p>\n<p>If you want to explore event-driven architecture further, including patterns like the ones we touched on (and beyond),\ncheck out my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course.\nIt covers these concepts in depth with practical examples, so you can apply EDA in real-world projects.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq",
            "title": "Event-Driven Architecture in .NET with RabbitMQ",
            "summary": "Learn how to implement flexible and resilient event-driven systems in .NET using RabbitMQ as a message broker with practical code examples for producers and…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_140.png",
            "date_modified": "2025-05-03T00:00:00.000Z",
            "date_published": "2025-05-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/refactoring-overgrown-bounded-contexts-in-modular-monoliths",
            "content_html": "<p>An overgrown bounded context shows up as one class doing billing, notifications, reporting, and user management at once.\nYou split it by grouping the code into logical subdomains, extracting a low risk one first, and replacing direct calls with domain events.\nData isolation comes later, one schema at a time.</p>\n<p>When you're building a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>, it's easy to let bounded contexts grow too large over time.\nWhat started as a clean domain boundary slowly turns into a dumping ground for unrelated logic.\nBefore you know it, you have a massive context responsible for users, payments, notifications, and reporting - all tangled together.</p>\n<p>This article is about tackling that mess.\nWe'll walk through how to identify an overgrown bounded context, and refactor it step-by-step into smaller, well-defined contexts.\nYou'll see practical techniques in action, with real .NET code and without theoretical fluff.</p>\n<h2>Identifying an Overgrown Context</h2>\n<p>You know you have a problem when:</p>\n<ul>\n<li>You're afraid to touch code because everything is interconnected</li>\n<li>The same entity is used for 4 unrelated use cases</li>\n<li>You see classes with 1000+ lines or services that do too much</li>\n<li>Business logic from different subdomains bleeds into each other</li>\n</ul>\n<p>Here's a classic example.</p>\n<p>We start with a <code>BillingContext</code> that now handles everything from notifications to reporting:</p>\n<pre><code class=\"language-csharp\">public class BillingService\n{\n    public void ChargeCustomer(int customerId, decimal amount) { ... }\n    public void SendInvoice(int invoiceId) { ... }\n    public void NotifyCustomer(int customerId, string message) { ... }\n    public void GenerateMonthlyReport() { ... }\n    public void DeactivateUserAccount(int userId) { ... }\n}\n</code></pre>\n<p>This service has no clear boundaries.\nIt mixes <strong>Billing</strong>, <strong>Notifications</strong>, <strong>Reporting</strong>, and <strong>User Management</strong> into a single, bloated class.\nChanging one feature could easily break another.</p>\n<h2>Step 1: Identify Logical Subdomains</h2>\n<p>We start by breaking this apart logically.\nThink like a product owner.</p>\n<p>Just ask: &quot;What domains are we really working with?&quot;</p>\n<p>Group the methods:</p>\n<ul>\n<li><strong>Billing</strong>: <code>ChargeCustomer</code>, <code>SendInvoice</code></li>\n<li><strong>Notifications</strong>: <code>NotifyCustomer</code></li>\n<li><strong>Reporting</strong>: <code>GenerateMonthlyReport</code></li>\n<li><strong>User Management</strong>: <code>DeactivateUserAccount</code></li>\n</ul>\n<p>Code within a <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>bounded context</strong></a> should model a coherent domain.\nWhen multiple domains are jammed into the same context, your architecture becomes misleading.</p>\n<p>You can validate these groupings by checking:</p>\n<ul>\n<li>Which parts of the system change together?</li>\n<li>Do teams use different vocabulary for each area?</li>\n<li>Would you give each domain to a different team?</li>\n</ul>\n<p>If yes, it's a sign you're dealing with distinct contexts.</p>\n<h2>Step 2: Extract One Context at a Time</h2>\n<p>Don't try to do it all at once.\nStart with something low-risk.</p>\n<p>Let's begin by extracting <strong>Notifications</strong>.</p>\n<p>Why <strong>Notifications</strong>?\nBecause it's a pure side-effect.\nIt doesn't impact business state, so it's easier to decouple safely.</p>\n<p>Create a new module and move the logic there:</p>\n<pre><code class=\"language-csharp\">// New module: Notifications\npublic class NotificationService\n{\n    public void Send(int customerId, string message) { ... }\n}\n</code></pre>\n<p>Then simplify the original <code>BillingService</code>:</p>\n<pre><code class=\"language-csharp\">public class BillingService\n{\n    private readonly NotificationService _notificationService;\n\n    public BillingService(NotificationService notificationService)\n    {\n        _notificationService = notificationService;\n    }\n\n    public void ChargeCustomer(int customerId, decimal amount)\n    {\n        // Charge logic...\n        _notificationService.Send(customerId, $&quot;You were charged ${amount}&quot;);\n    }\n}\n</code></pre>\n<p>This works. But now <strong>Billing</strong> <em>depends on</em> <strong>Notifications</strong>.\nThat's a coupling we want to avoid long-term.</p>\n<p>Why?\nBecause a failure in <strong>Notifications</strong> could block a billing operation.\nIt also means <strong>Billing</strong> can't evolve independently.</p>\n<p>Let's decouple with <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>domain events</strong></a>:</p>\n<pre><code class=\"language-csharp\">public class CustomerChargedEvent\n{\n    public int CustomerId { get; init; }\n    public decimal Amount { get; init; }\n}\n\n// Module: Billing\npublic class BillingService\n{\n    private readonly IDomainEventDispatcher _dispatcher;\n\n    public BillingService(IDomainEventDispatcher dispatcher)\n    {\n        _dispatcher = dispatcher;\n    }\n\n    public void ChargeCustomer(int customerId, decimal amount)\n    {\n        // Charge logic...\n        _dispatcher.Dispatch(new CustomerChargedEvent\n        {\n            CustomerId = customerId,\n            Amount = amount\n        });\n    }\n}\n\n// Module: Notifications\npublic class CustomerChargedEventnHandler : IDomainEventHandler&lt;CustomerChargedEvent&gt;\n{\n    public Task Handle(CustomerChargedEvent @event)\n    {\n        // Send notification\n    }\n}\n</code></pre>\n<p>Now <strong>Billing</strong> doesn't even <em>know</em> about <strong>Notifications</strong>.\nThat's real modularity.\nYou can replace, remove, or enhance the <strong>Notifications</strong> module without touching <strong>Billing</strong>.</p>\n<h2>Step 3: Migrate Data (If Needed)</h2>\n<p>Most monoliths start with a single database.\nThat's fine.\nBut real modularity comes when each module controls its own <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>schema</strong></a>.</p>\n<p>Why?\nBecause the database structure reflects ownership.\nIf everything touches the same tables, it's hard to enforce boundaries.</p>\n<p>You don't have to do it all at once.\nStart with:</p>\n<ul>\n<li>Creating a <a href=\"https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application\"><strong>separate <code>DbContext</code> per module</strong></a></li>\n<li>Gradually migrate the tables to their own schemas</li>\n<li>Read-only projections or database views for cross-context reads</li>\n</ul>\n<pre><code class=\"language-csharp\">// Module: Billing\npublic class BillingDbContext : DbContext\n{\n    public DbSet&lt;Invoice&gt; Invoices { get; set; }\n}\n\n// Module: Notifications\npublic class NotificationsDbContext : DbContext\n{\n    public DbSet&lt;NotificationLog&gt; Logs { get; set; }\n}\n</code></pre>\n<p>This separation enables independent schema evolution.\nIt also makes <a href=\"https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing\"><strong>testing</strong></a> faster and safer.</p>\n<p>When migrating, use a transitional phase where both contexts read from the same underlying data.\nOnly switch write paths when confidence is high.</p>\n<h2>Step 4: Repeat for Other Areas</h2>\n<p>Apply the same playbook.\nTarget a clean split per subdomain.</p>\n<p>Next up: <strong>Reporting</strong> and <strong>User Management</strong>.</p>\n<p>Before:</p>\n<pre><code class=\"language-csharp\">billingService.GenerateMonthlyReport();\nbillingService.DeactivateUserAccount(userId);\n</code></pre>\n<p>After:</p>\n<pre><code class=\"language-csharp\">reportingService.GenerateMonthlyReport();\nuserService.DeactivateUser(userId);\n</code></pre>\n<p>Or via events:</p>\n<pre><code class=\"language-csharp\">_dispatcher.Dispatch(new MonthEndedEvent());\n_dispatcher.Dispatch(new UserInactiveEvent(userId));\n</code></pre>\n<p>The goal here isn't just technical cleanliness - it's clarity.\nAnyone looking at your solution should know what each module is responsible for.</p>\n<p>And remember: <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>boundaries</strong></a> should be enforced by code, not just by folder structure.\nDifferent projects, separate EF models, and <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>explicit interfaces</strong></a> help enforce the split.\n<a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>Architecture tests</strong></a> can also help ensure that modules don't break their boundaries.</p>\n<h2>Takeaway</h2>\n<p>Once you've finished the refactor, you'll have:</p>\n<ul>\n<li><strong>Smaller services</strong> focused on one job</li>\n<li><strong>Decoupled modules</strong> that evolve independently</li>\n<li><strong>Better tests</strong> and easier debugging</li>\n<li><strong>Bounded contexts</strong> that actually match the domain</li>\n</ul>\n<p>This is more than structure, it's design that supports change.\nYou get loose coupling, testability, and clearer mental models.</p>\n<p>You don't need <a href=\"https://milanjovanovic.tech/blog/modular-monolith-vs-microservices\"><strong>microservices</strong></a> to get modularity.\nYou need to treat your monolith like a set of cooperating, isolated parts.</p>\n<p>Start with one module.\nShip the change.\nRepeat.</p>\n<p>Want to go deeper into modular monolith design?\nMy full video course, <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>,\nwalks you through building a real-world system from scratch - with clear boundaries, isolated modules, and practical patterns that scale.\nJoin 1,800+ students and start building better systems today.</p>\n<p>That's all for today.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/refactoring-overgrown-bounded-contexts-in-modular-monoliths",
            "title": "Refactoring Overgrown Bounded Contexts in Modular Monoliths",
            "summary": "Learn how to untangle bloated services and split them into clean, modular bounded contexts in a .NET modular monolith - one practical refactor at a time.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_139.png",
            "date_modified": "2025-04-26T00:00:00.000Z",
            "date_published": "2025-04-26T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/understanding-microservices-core-concepts-and-benefits",
            "content_html": "<p>Microservices are services modeled around a business domain and deployed independently of each other.\nEach service owns its own data behind a well-defined interface and talks to the others over the network.\nThey buy you options, like scaling one service on its own, and you pay for them with network failures, operational overhead, and eventual consistency.</p>\n<p>I've been revisiting Sam Newman's excellent book <a href=\"https://www.oreilly.com/library/view/monolith-to-microservices/9781492047834/\">&quot;Monolith to Microservices&quot;</a> recently,\nand it's reminded me just how transformative this architectural approach can be <strong>when applied correctly</strong>.</p>\n<p>As someone who's implemented microservices in various organizations, I wanted to share some valuable insights I've gained through both study and practical experience.</p>\n<p>What exactly are microservices, and why might they be the right architectural choice for your organization?</p>\n<p>Let's dive into the core concepts and benefits of microservices architecture.</p>\n<h2>What Are Microservices?</h2>\n<p>Microservices are <strong>independently deployable</strong> services modeled around a <strong>business domain</strong>.\nBusiness domain is key here, but more on that later.\nThey communicate with each other via networks and offer many options for solving complex architectural problems.</p>\n<p>Think of microservices as small, focused teams rather than a large department.\nEach team has a specific responsibility, operates somewhat independently, and communicates clearly with other teams when needed.\nInstead of one massive codebase that handles everything, you have multiple smaller codebases, each focusing on a specific business capability.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_138/microservices_architecture.png\" alt=\"Microservices architecture showing an event management system.\">\n<p>As Sam Newman defines them in &quot;Monolith to Microservices&quot;:</p>\n<blockquote>\n<p>Microservices are independently deployable services modeled around a business domain.\nThey communicate with each other via networks, and as an architecture choice offer many options for solving the problems you may face.</p>\n</blockquote>\n<p>Microservices give you a strategy to design a modular system and decompose it into <a href=\"https://milanjovanovic.tech/blog/bounded-context-ddd-explained\"><strong>bounded contexts</strong></a>.\nHowever, the problem I often see is that developers use microservices to enforce code boundaries.\nThis is a mistake.\nWe'll fix that in a moment.</p>\n<h2>Key Characteristics of Microservices</h2>\n<p>Let's explore some of the key characteristics that define microservices:</p>\n<h3>Independent Deployability</h3>\n<p>You can make changes to a microservice and deploy it to production without having to deploy anything else.\nThis isn't just a theoretical ability - it's a discipline you practice for most of your releases.</p>\n<p>The value here is significant: <strong>smaller deployments</strong> carry <strong>less risk</strong>, enable <strong>faster release cycles</strong>, and allow teams to test their changes in isolation.</p>\n<p>When a critical bug appears in one service, you can fix and deploy just that service rather than orchestrating a full system release.\nThis is especially valuable in large systems where coordinating releases can be a logistical nightmare.</p>\n<h3>Business Domain Focus</h3>\n<p>Microservices are organized around <a href=\"https://milanjovanovic.tech/blog/screaming-architecture\"><strong>business capabilities</strong></a> rather than technical layers.\nInstead of having separate frontend, backend, and database teams (and the coordination that requires),\nyou might have teams dedicated to &quot;Event Management,&quot; &quot;Customer Accounts,&quot; or &quot;Attendance.&quot;</p>\n<p>This alignment makes it easier to implement business functionality changes since all the related code - from UI to data storage - is grouped together.\nWhen a business requirement changes, you can often change just one service rather than coordinating changes across multiple layers.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_138/event_storming.png\" alt=\"Event storming whiteboard with events grouped into logical boundaries.\">\n<h3>Data Ownership</h3>\n<p>Microservices <strong>encapsulate data storage</strong> and retrieval, exposing data only via <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>well-defined interfaces</strong></a>.\nDatabases are hidden inside the service boundary rather than shared between services.</p>\n<p>This stands in stark contrast to traditional approaches where multiple applications share a common database, often leading to tight coupling and risky schema changes.</p>\n<p>When a service owns its data exclusively, it can:</p>\n<ul>\n<li>Evolve its internal data model without breaking other services</li>\n<li>Implement the most appropriate storage technology for its needs</li>\n<li>Provide a stable API for other services to access its data.</li>\n</ul>\n<p>It's not uncommon for multiple services to share the same database, but you're giving up some of the benefits of microservices by doing so.\nIn practice, one database per service is the most common approach.</p>\n<h3>Network Communication</h3>\n<p>Services communicate with each other via networks, making microservices a form of distributed system.\nThis network-based communication could use <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>REST APIs</strong></a>, message queues, gRPC, GraphQL, or other protocols depending on the specific needs.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_138/network_communication.png\" alt=\"A simple diagram with two boxes representing microservices that communicate over a network.\">\n</div>\n<p>This explicit communication over networks allows services to be deployed independently and even run on different infrastructure.\nBut it also means dealing with network latency, potential failures, and serialization concerns.</p>\n<p>Teams building microservices need to carefully design their <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>inter-service communication patterns</strong></a>\nto balance performance, reliability, and flexibility.</p>\n<h2>The Origins of Microservices</h2>\n<p>The term &quot;microservices&quot; has an interesting origin story.\nIn 2011, a software consultant named James Lewis became interested in what he called &quot;micro-apps&quot; - small services optimized to be easily replaceable.</p>\n<p>The distinguishing feature was how small in scope these services were.\nSome could be written or rewritten in just a few days.\nAs discussions evolved, the term &quot;microservices&quot; was adopted since these weren't self-contained applications but rather services working together.</p>\n<p>It's worth noting that while &quot;micro&quot; is in the name, the size of a microservice isn't its defining characteristic.\nRather, it's about having services with well-defined boundaries that can be developed, deployed, and scaled independently.</p>\n<h2>Key Benefits of Microservices</h2>\n<p>What are the benefits of adopting a microservices architecture?</p>\n<p>Why should you consider it for your organization?</p>\n<p>Let's explore some of the key advantages:</p>\n<h3>Flexibility and Adaptability</h3>\n<p><strong>Microservices give you options</strong>.\nThey provide flexibility in how you can scale, evolve, and maintain your system over time.</p>\n<ul>\n<li>When business requirements change, you can modify just the affected services rather than risking changes to the entire system.</li>\n<li>New capabilities can be introduced as new services without disrupting existing functionality.</li>\n<li>As your understanding of the domain grows, service boundaries can evolve to better reflect that understanding.</li>\n</ul>\n<p>This ability to evolve incrementally is particularly valuable in rapidly changing business environments where time-to-market is critical.</p>\n<h3>Technology Diversity</h3>\n<p>With microservices, you can mix and match technology stacks.\nEach service can use the programming language, database, or framework best suited for its specific requirements.\nThis practice is known as polyglot programming and <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>polyglot persistence</strong></a>.</p>\n<p>For example, a recommendation engine might use Python with specialized machine learning libraries.\nOn the other hand, a transaction processing service might use .NET for its strong typing and performance characteristics.</p>\n<p>A reporting service might use a columnar database optimized for analytics,\nwhile a user profile service could use a document database that better fits its data model.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_138/polyglot_persistence.png\" alt=\"Event storming whiteboard with events grouped into logical boundaries.\">\n</div>\n<p>This flexibility allows teams to choose the right tool for each job rather than compromising on a one-size-fits-all approach.</p>\n<h3>Parallel Development</h3>\n<p>Multiple teams can work on different services simultaneously without stepping on each other's toes.\nThis parallelization can significantly accelerate development velocity in larger organizations.</p>\n<p>Each team can maintain its own release schedule, make technology decisions, and optimize for their specific service's needs without coordinating with every other team.\nI've seen firsthand how this autonomy can reduce dependencies between teams, minimizing bottlenecks and wait times.</p>\n<p>Organizations commonly structure their teams around services or groups of related services.\nYou might know this concept as <a href=\"https://en.wikipedia.org/wiki/Conway%27s_law\">Conway's Law</a>:</p>\n<blockquote>\n<p>Organizations, who design systems, are constrained to produce designs which are copies of the communication structures of these organizations.</p>\n</blockquote>\n<h3>Targeted Scaling</h3>\n<p>You can scale just the services that need it, rather than scaling the entire system.\nThis provides more efficient resource utilization and can reduce operational costs.</p>\n<p>For example, if your product catalog needs to handle high traffic during a sale, you can scale just the catalog service without scaling your payment processing service.</p>\n<p>This granular scaling becomes especially valuable as systems grow and different components have different performance characteristics.\nSome services might be CPU-intensive while others are memory-intensive, and with microservices, you can optimize the infrastructure for each service's specific needs.</p>\n<p>This approach can lead to significant cost savings compared to <a href=\"https://milanjovanovic.tech/blog/scaling-monoliths-a-practical-guide-for-growing-systems\"><strong>scaling a monolith</strong></a>,\nwhere all components must scale together regardless of their individual requirements.</p>\n<h3>Organizational Alignment</h3>\n<p>Microservices can help align your technical architecture with your organizational structure.\nTeams can own specific services that correspond to their business domain expertise, promoting clearer ownership and accountability.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_138/evently_domain.png\" alt=\"UML diagram showing the main bounded contexts in the system and their parts.\">\n</div>\n<p>This alignment reduces handoffs and coordination costs between teams, as each team has clear boundaries of responsibility.\nIt supports Conway's Law in a positive way.\nInstead of having your communication structure accidentally create your architecture, you deliberately design both your teams and your services around business capabilities.</p>\n<p>This approach can lead to more stable team structures and software boundaries over time, as business domains tend to evolve more slowly than technical implementations.</p>\n<h2>Challenges to Consider With Microservices</h2>\n<p>While microservices offer numerous benefits, they're not without challenges:</p>\n<h3>Distributed System Complexity</h3>\n<p>Network communication introduces latency, reliability challenges, and makes debugging more difficult.\nServices must handle network failures gracefully, implement retries with backoff strategies,\nand deal with the reality that a request might succeed but the response might get lost.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>Distributed tracing</strong></a> becomes essential to understand how requests flow through the system.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_138/distributed_trace.png\" alt=\"Distributed trace.\">\n<p>You'll need to develop strategies for handling partial system failures.\nConcepts like circuit breakers and bulkheads become part of your everyday vocabulary.</p>\n<h3>Operational Overhead</h3>\n<p>Managing many services requires robust <a href=\"https://milanjovanovic.tech/blog/streamlining-dotnet-9-deployment-with-github-actions-and-azure\"><strong>deployment pipelines</strong></a>, monitoring, and debugging tools.\nYou'll need to invest in automation for deployment, <a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core\"><strong>health checking</strong></a>, scaling,\nand perhaps <a href=\"https://milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery\"><strong>service discovery</strong></a>.\nEach service needs monitoring, logging, and alerting.</p>\n<p>This overhead can be substantial.\nOrganizations successfully running microservices typically have a strong DevOps culture and tooling to manage this complexity.</p>\n<h3>Data Consistency</h3>\n<p>Maintaining consistency across service boundaries becomes more challenging without the safety of <a href=\"https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core\"><strong>database transactions</strong></a>.</p>\n<p>Implementing business processes that span multiple services often requires eventual consistency models and compensation mechanisms to handle failures.\nYou'll need to design your services with <a href=\"https://milanjovanovic.tech/blog/implementing-idempotent-rest-apis-in-aspnetcore\"><strong>idempotency</strong></a> in mind and may need to implement patterns like the\n<a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>Saga pattern</strong></a> to manage distributed transactions.</p>\n<p>These approaches add complexity but can actually lead to more resilient systems when implemented correctly.</p>\n<h3>Service Coordination</h3>\n<p>Orchestrating workflows that span multiple services requires careful design.\nSimple processes in a monolith can become complex choreographies in a microservice architecture.</p>\n<p>You'll need to decide whether to use <a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\"><strong>orchestration</strong></a> (where a central service directs the process)\nor <a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\"><strong>choreography</strong></a> (where services react to events without central coordination).\nThese patterns have different trade-offs in terms of coupling, resilience, and observability.</p>\n<p>Designing these cross-service workflows often reveals subdomain boundaries you might have missed in initial modeling.</p>\n<h2>Key Takeaway</h2>\n<p>From my experience, the most important thing to remember is that microservices ultimately <strong>buy you options</strong>.\nThey provide flexibility but come with costs.</p>\n<p>Are these costs worth the options you want to exercise?</p>\n<p>For organizations with a large engineering team working on a complex system that needs to evolve quickly, microservices may be worth the overhead.\nFor smaller teams or systems with more stable requirements, a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>well-designed monolith</strong></a> might be more appropriate.\nForget about resume-driven development for a moment.\nIt's about what solves your specific problems with <strong>acceptable trade-offs</strong>.</p>\n<p>In my work, I've found that the most successful microservice adoptions start small, often by <a href=\"https://milanjovanovic.tech/blog/when-to-extract-module-to-microservice\"><strong>breaking off just one or two services</strong></a> from a monolith.\nThen, gradually expanding as the organization builds the necessary skills and infrastructure.\nThis evolutionary approach reduces risk and allows teams to learn as they go.</p>\n<p>I always like to reflect on these questions:</p>\n<ol>\n<li>What parts of the current architecture would benefit most from independent deployability?</li>\n<li>What challenges might the organization face when adopting microservices?</li>\n<li>How well does the current system align with business domains?</li>\n</ol>\n<p>If you want to dive deeper into building microservices - but starting from a monolith, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.\nThere's an entire chapter dedicated to developing microservices, including advanced techniques such as API gateways, using message queues, and system integration testing.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/understanding-microservices-core-concepts-and-benefits",
            "title": "Understanding Microservices: Core Concepts and Benefits",
            "summary": "What are microservices, and why might they be the right architectural choice for your organization?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_138.png",
            "date_modified": "2025-04-19T00:00:00.000Z",
            "date_published": "2025-04-19T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide",
            "content_html": "<p><strong>Vector search</strong> is changing how we find information.\nUnlike old search methods that look for exact words, vector search finds content based on meaning.\nThis makes search results more helpful and human-like.</p>\n<p>When you search for &quot;quick healthy breakfast ideas,&quot; vector search can find articles about &quot;nutritious morning meals&quot; even if they don't use your exact words.\nThis happens because vector search understands what you mean, not just what you type.</p>\n<p>In this week's newsletter, we'll break down how vector search works and why it's important.</p>\n<h2>Understanding Vector Embeddings</h2>\n<p>At the heart of vector search are <strong>vector embeddings</strong>.\nThese are lists of numbers that represent data.\nBut how do we get from words or images to numbers?</p>\n<p>Here's how it works:</p>\n<ol>\n<li>We feed text, images, or other data into a <a href=\"https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai\"><strong>large language model (LLM)</strong></a></li>\n<li>The LLM turns each piece of data into a list of numbers (a vector)</li>\n<li>These numbers capture the meaning of the data</li>\n<li>Similar things get similar number patterns</li>\n</ol>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_137/vector_space.png\" alt=\"Vector space visualization.\">\n</div>\n<p>Think of each number in the vector as describing one aspect of the data.\nWith hundreds or thousands of these numbers, vectors can capture complex meanings and relationships.</p>\n<p>For example, in vector form, the words &quot;lion&quot; and &quot;bobcat&quot; would have similar number patterns because they refer to similar animals.\nMeanwhile, &quot;cat&quot; would have a different pattern, though still somewhat similar since it's also a feline.</p>\n<h2>How Vector Search Works</h2>\n<p>When you search using vector search:</p>\n<ol>\n<li>Your search question gets turned into a vector</li>\n<li>The system compares this vector to all the vectors in its database</li>\n<li>It finds the vectors that are most similar to your search vector</li>\n<li>It returns the data connected to those similar vectors</li>\n</ol>\n<p>To find similar vectors, the system measures how close they are to each other.\nThink of each vector as a point in space - the closer two points are, the more similar their meanings.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_137/vector_search.png\" alt=\"Vector search visualization.\">\n<h3>Vector Databases</h3>\n<p>To make vector search work well with lots of data, we need special storage systems called <strong>vector databases</strong>.\nThese databases are built to store and quickly search through millions or billions of vectors.</p>\n<p>Vector databases do more than just store vectors - they organize them in smart ways that make searching faster.\nThey use special methods called &quot;Approximate Nearest Neighbor&quot; (ANN) algorithms that can find similar vectors without checking every single one in the database.</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_137/vector_databases_landscape.png\" alt=\"Vector databases landscape.\">\n  </div>\n  <figcaption>\n    Source: <a href=\"https://www.infracloud.io/blogs/vector-databases-beginners-guide/\">What are Vector Databases? A Beginner's\nGuide</a>\n  </figcaption>\n</figure>\n<p>These databases also store the original content along with its vector, so when you get search results,\nyou see the actual text, images, or other data you were looking for.\nPopular vector databases include Weaviate, Pinecone, and Qdrant.\nBut you can also turn PostgreSQL into a vector database using the pgvector extension.</p>\n<p>The combination of vector embeddings and vector databases makes search extremely fast, even with millions of items to search through.\nThis speed makes vector search practical for real-world applications where users expect instant results.</p>\n<h2>Key Differences from Traditional Search</h2>\n<p>Traditional keyword search works like this:</p>\n<ul>\n<li>You type &quot;red shoes&quot;</li>\n<li>The system finds pages with the words &quot;red&quot; and &quot;shoes&quot;</li>\n<li>Results that mention these words more often rank higher</li>\n</ul>\n<p>This approach has problems:</p>\n<ul>\n<li>It misses related terms (&quot;scarlet footwear&quot;)</li>\n<li>It doesn't understand context</li>\n<li>It can't handle questions well</li>\n</ul>\n<p>An improvement to keyword search is <a href=\"https://milanjovanovic.tech/blog/how-i-implemented-full-text-search-on-my-website\"><strong>full-text search</strong></a>, which looks at the whole text of documents.\nHowever, this still has some shortcomings that vector search solves.</p>\n<p>Vector search fixes these issues by focusing on meaning rather than exact words.\nIt can:</p>\n<ul>\n<li>Find content with related concepts</li>\n<li>Understand the context of your search</li>\n<li>Return helpful results even for complex questions</li>\n</ul>\n<p>This is why vector search powers many <a href=\"https://milanjovanovic.tech/blog/rag-system-dotnet\"><strong>modern AI applications</strong></a>.\nIt helps chatbots find relevant information and makes recommendation systems more accurate.</p>\n<h2>Summary</h2>\n<p>Vector search represents a major step forward in how computers understand and retrieve information.\nBy converting data into number patterns that capture meaning, vector search can find connections that keyword search would miss.</p>\n<p>This technology is behind many of the smart search features we now take for granted -\nfrom finding similar products in online stores to helping AI assistants answer our questions.\nAs AI continues to advance, vector search will play an increasingly important role in helping us navigate the growing sea of digital information.</p>\n<p>While not perfect, vector search bridges the gap between how computers store data and how humans think about meaning -\nmaking our digital tools more helpful and intuitive to use.</p>\n<p>In a future newsletter, we'll dive into the practical side of vector search.\nYou'll learn how to implement your own vector search system using popular tools and libraries, with step-by-step code examples and best practices.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/what-is-vector-search-a-concise-guide",
            "title": "What is Vector Search? A Concise Guide",
            "summary": "Vector search finds information based on meaning rather than exact keywords, delivering more intuitive results by converting content into numerical vectors…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_137.png",
            "date_modified": "2025-04-12T00:00:00.000Z",
            "date_published": "2025-04-12T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/mediatr-and-masstransit-going-commercial-what-this-means-for-you",
            "content_html": "<p>MediatR, AutoMapper, and MassTransit all announced moves to commercial licensing in 2025.\nThe announcements kept the existing open source versions available, with MassTransit v8 security patches planned through 2026.\nYour options are to buy a license, stay on the current version, switch to alternatives like Mapster or Rebus, or write the functionality yourself.</p>\n<p>Big changes are happening in the .NET ecosystem.\nThree powerhouse libraries - MediatR, AutoMapper, and MassTransit - are moving to commercial licenses.\nNot so long ago, Fluent Assertions also announced its plans to move to a commercial license.</p>\n<p>As someone who's built countless systems with these tools over the past decade, I have thoughts.\nAnd some strong opinions.</p>\n<h2>The Libraries We Love (And Sometimes Hate)</h2>\n<p>If you're a .NET developer, you likely use at least one of these:</p>\n<p><a href=\"https://github.com/AutoMapper/AutoMapper\"><strong>AutoMapper</strong></a> (794.7M downloads) transforms objects from one type to another.\nIt removes mountains of tedious mapping code that nobody enjoys writing.\nOne line replaces twenty.\n<strong>I personally despise AutoMapper and mapping libraries in general</strong>, but I can't deny their popularity.</p>\n<p><a href=\"https://github.com/jbogard/MediatR\"><strong>MediatR</strong></a> (286.6M downloads) implements the <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\"><strong>mediator pattern</strong></a>.\nIt decouples requests from the objects handling them, promoting separation of concerns.\nThere's also the <a href=\"https://milanjovanovic.tech/blog/mediatr-pipeline-behaviors\"><strong>pipeline behavior</strong></a> feature, which allows you to add cross-cutting concerns.\nI'm a huge fan and use it regularly in my projects.</p>\n<p><a href=\"https://github.com/MassTransit/MassTransit\"><strong>MassTransit</strong></a> (130.0M downloads) makes distributed messaging simple.\nIt wraps message brokers like <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>RabbitMQ and Azure Service Bus</strong></a> with an elegant API.\nBuilding event-driven systems becomes approachable.\nThis is another tool I love and often recommend.</p>\n<p>These libraries aren't just popular - they're transformative.\nThey've shaped how we build .NET applications.</p>\n<h2>The Maintainer's Reality</h2>\n<p>Both announcements tell a similar story.</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_136/mediatr_automapepr_announcement.png\" alt=\"AutoMapper and MediatR Going Commercial.\">\n  </div>\n  <figcaption>\n    Source: <a href=\"https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/\">AutoMapper and MediatR Going\nCommercial</a>\n  </figcaption>\n</figure>\n<p>Jimmy Bogard (AutoMapper, MediatR) writes:</p>\n<blockquote>\n<p>You can see exactly where my contributions cratered and flat-lined. And that's just commits—issues, PRs, discussions, all my time dried up.</p>\n</blockquote>\n<p>His OSS work was previously sponsored by his former employer.\nWhen he went independent, that support vanished.\nHis focus shifted to his consulting business.</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_136/masstransit_announcement.png\" alt=\"Announcing MassTransit v9.\">\n  </div>\n  <figcaption>\n    Source: <a href=\"https://masstransit.io/introduction/v9-announcement\">Announcing MassTransit\nv9</a>\n  </figcaption>\n</figure>\n<p>Similarly, MassTransit has grown from &quot;a single assembly that supported MSMQ&quot; in 2007 to over thirty NuGet packages.\nIts success created demands that are impossible to meet through volunteer work alone:</p>\n<ul>\n<li>Full-time development resources</li>\n<li>Enterprise-grade support</li>\n<li>Long-term sustainability</li>\n</ul>\n<p>Both maintainers face the same dilemma: how do you support widely used libraries when nobody pays you to do it?</p>\n<h2>The Commercial Transition</h2>\n<p>Here's what's happening:</p>\n<p><strong>AutoMapper and MediatR</strong>: Jimmy hasn't shared specific timing or pricing yet.\nHe states, &quot;Short term, nothing will change.&quot;</p>\n<p><strong>MassTransit</strong>: Moving from v8 (open source) to v9 (commercial) with this timeline:</p>\n<ul>\n<li>Q3 2025: v9 prerelease for early adopters</li>\n<li>Q1 2026: v9 official release under commercial license</li>\n<li>Through 2026: v8 security patches continue</li>\n</ul>\n<p>MassTransit's pricing targets:</p>\n<ul>\n<li>Small/medium businesses: $400/month or $4000/year</li>\n<li>Large enterprises: $1200/month or $12000/year</li>\n<li>Support for ISVs and consultants who build client applications</li>\n</ul>\n<p>None of this is set in stone.\nThe pricing aspect should be final by the time the commercial version is released.</p>\n<h2>Why I Respect This Decision</h2>\n<p>Both maintainers waited over a decade before making this move.\nThey've contributed immense value to our community for free.</p>\n<p>Their announcements show careful consideration.\nThey're not abandoning users:</p>\n<ul>\n<li>Existing versions remain open source</li>\n<li>Security patches will continue</li>\n<li>Commercial licenses support sustainable development</li>\n</ul>\n<p>I honestly hope none of the above changes in the future.\nThe work they do is valuable.</p>\n<p>Writing these libraries from scratch would cost your team far more than their license fees.</p>\n<h2>Your Options Now (With My Take)</h2>\n<p>If your project uses these libraries, you have choices:</p>\n<ol>\n<li>\n<p><strong>Purchase the commercial license</strong><br>\nThis supports continued development and gets you new features and official support.</p>\n</li>\n<li>\n<p><strong>Stay on the current open source version</strong><br>\nMassTransit v8 and current MediatR/AutoMapper will remain available.\nSecurity patches will continue through 2026 for MassTransit.\nFor MassTransit specifically, I'd consider staying on v8 for the long term if possible.</p>\n</li>\n<li>\n<p><strong>Switch to alternatives</strong><br>\nFor AutoMapper: consider <a href=\"https://github.com/MapsterMapper/Mapster\">Mapster</a> or <strong>manual mapping</strong> (my recommendation).</p>\n<p>For MediatR: explore <a href=\"https://github.com/FastEndpoints/FastEndpoints\">FastEndpoints</a> or <a href=\"https://milanjovanovic.tech/blog/stop-conflating-cqrs-and-mediatr\"><strong>build a simple mediator yourself</strong></a>.</p>\n<p>For MassTransit: look at raw client libraries like <a href=\"https://www.nuget.org/packages/rabbitmq.client/\">RabbitMQ.Client</a> and\n<a href=\"https://www.nuget.org/packages/Azure.Messaging.ServiceBus\">Azure.Messaging.ServiceBus</a>,\nand another option to consider is <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq\"><strong>Rebus</strong></a>.</p>\n</li>\n<li>\n<p><strong>Write equivalent functionality yourself</strong><br>\nMediatR isn't too complex to build on your own.\nI recommend giving it a try as an excellent coding exercise - it's probably the simplest way to move away from MediatR.</p>\n<p>For AutoMapper, many teams have deep integrations with business logic in custom mappers.\nThis makes extracting and replacing it difficult.\nExpect significant tech debt if you don't address this.</p>\n<p>MassTransit, on the other hand, does so many things (and does them well) that migrating away would be challenging.\n<a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit\"><strong>Saga support</strong></a> or the <a href=\"https://milanjovanovic.tech/blog/request-response-messaging-pattern-with-masstransit\"><strong>request-response messaging</strong></a>\nfeatures are hard to replicate.\nThe only real alternative is diving into raw client libraries for your chosen message transport.</p>\n</li>\n</ol>\n<p>Each option involves tradeoffs. The right choice depends on your project needs and budget.</p>\n<h2>A Shift to Fundamentals</h2>\n<p>These changes have made me reflect on something important: we should never lose sight of fundamentals.</p>\n<p>We've been pampered and spoiled by these awesome libraries.\nIt's easy to lose sight of the actual problems they're solving and how they work under the hood.\nPeople know how to use MediatR, but they don't understand the mechanisms behind it.</p>\n<p>The same goes for MassTransit.\nIt abstracts away so many complexities of working with message brokers that it's possible to use it without knowing how RabbitMQ or Azure Service Bus actually works.</p>\n<p>Remember this: using a library that abstracts something away doesn't excuse you from understanding the patterns and tools you're using.\nThe <strong>fundamentals are still there</strong> and always have been.\nThis might be the perfect opportunity to deepen your knowledge of what's happening beneath these abstractions.</p>\n<h2>A Reality Check</h2>\n<p>Open source isn't free. Someone pays - either with time or money.</p>\n<p>I'm honestly tired of seeing developers complain about these changes.\nWho are they to demand software for free?\nThe entitlement is astounding.\nThese maintainers have provided immense value for over a decade without asking for anything in return.</p>\n<p>We've enjoyed years of exceptional tooling without directly funding it.\nNow we face a reckoning.</p>\n<p>As businesses reap massive productivity gains from these libraries, it's reasonable to ask: shouldn't some of that value flow back to the creators?</p>\n<p>I hope these projects thrive under their new models.\nThey've earned support after years of thankless work.</p>\n<p>Both my <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> and\n<a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> courses currently use MediatR and MassTransit extensively.\nI plan to keep them on MediatR v12 and MassTransit v8 in the short term.\nHowever, I'll also be updating them to show migration paths away from these libraries.</p>\n<p>What's your take?\nWill you stick with the open versions, pay for licenses, or explore alternatives?</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/mediatr-and-masstransit-going-commercial-what-this-means-for-you",
            "title": "MediatR and MassTransit Going Commercial: What This Means For You",
            "summary": "MediatR, AutoMapper, and MassTransit are moving to commercial licenses after more than a decade of free open source.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_136.png",
            "date_modified": "2025-04-05T00:00:00.000Z",
            "date_published": "2025-04-05T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery",
            "content_html": "<p>.NET Aspire skips the centralized service registry and does service discovery through configuration.\nYou declare the relationship in the App Host with <code>WithReference</code>, and Aspire injects the endpoints for each service name.\nYour <code>HttpClient</code> then targets <code>http://weather-api</code> and resolves to the right address in every environment.</p>\n<p>Unless you've been living under a rock, you know that <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview\">.NET Aspire</a>\nis changing (for the better) how we build distributed applications in .NET.\nA simple way to think about Aspire: it makes all the difficult things in software development easy.</p>\n<p>.NET Aspire is a cloud-native application stack that simplifies the development and deployment of distributed applications.\nOne of the key challenges when building multi-service applications is building reliable <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>communication between services</strong></a>.</p>\n<p>In this week's newsletter, I want to focus on one aspect of .NET Aspire - <a href=\"https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul\"><strong>service discovery</strong></a>.\nService discovery lets our services figure out how to locate other services they want to integrate with.\n.NET Aspire tackles this challenge with a simple, configuration-based approach that reduces complexity and boilerplate code.</p>\n<h2>Understanding Service Discovery</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/service-discovery\"><strong>Service discovery</strong></a>\nis the process by which services in a distributed application locate and communicate with each other.\nAs applications scale and evolve, keeping track of service endpoints becomes increasingly challenging.\nServices might run on different ports during development or be deployed to different environments in production,\nmaking hard-coded service URLs impractical.</p>\n<p>Traditional approaches to service discovery often introduce complexity:</p>\n<ul>\n<li>Manual configuration of service endpoints that must be updated as environments change</li>\n<li>Complex intermediary systems that require additional maintenance</li>\n<li>Custom code to handle service resolution and connection management</li>\n</ul>\n<p>While many service discovery implementations rely on centralized registries,\n.NET Aspire takes a different approach by leveraging application configuration to connect services.\nThis design choice simplifies the development experience while maintaining flexibility for various deployment scenarios.\nAspire automatically takes care of wiring up the correct service URLs and injecting them into your application settings.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_135/aspire_service_discovery.png\" alt=\"Diagram explaining how the .NET Aspire service discovery mechanism works.\">\n<h2>Practical Example of Service Discovery</h2>\n<p>To understand how .NET Aspire handles service discovery, let's look at a practical example of an application with multiple services:</p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\n// Add services to the app\nvar apiService = builder.AddProject&lt;Projects.WeatherApi&gt;(&quot;weather-api&quot;);\nvar webFrontend = builder.AddProject&lt;Projects.WebFrontend&gt;(&quot;web-frontend&quot;)\n    .WithReference(apiService);\n    \nbuilder.Build().Run();\n</code></pre>\n<p>In this App Host definition, we're creating two services: a weather API and a web frontend.\nThe <code>.WithReference()</code> method establishes a connection between these services, which enables service discovery.\nThis simple declaration tells <a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>.NET Aspire</strong></a>\nthat the web frontend depends on the weather API and needs to communicate with it.\nNote that the <code>web-frontend</code> has to be a server-side application (like <a href=\"https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models\">Blazor Server</a>)\nfor Aspire to be able to inject the service URL.</p>\n<p>With this configuration in place, the web frontend can now reach the API using the service name <code>weather-api</code>\nwithout additional service discovery code:</p>\n<pre><code class=\"language-csharp\">// Configures the default Aspire services, including service discovery\nbuilder.AddServiceDefaults();\n\n// In Program.cs of the web-frontend project\nbuilder.Services.AddHttpClient(&quot;weather-api&quot;, (_, client) =&gt; {\n    // The service name &quot;weather-api&quot; automatically resolves to the correct address\n    client.BaseAddress = new Uri(&quot;http://weather-api&quot;);\n});\n</code></pre>\n<p>This works because .NET Aspire manages the mapping between service names and their actual endpoints.\nWhen the application runs, service discovery ensures that requests to <code>http://weather-api</code> are routed to the appropriate destination.</p>\n<h2>Service Discovery Under the Hood</h2>\n<p>The previous example contains a bit of &quot;magic&quot; that might not be immediately clear.\nThe <code>AddServiceDefaults()</code> method configures the default services for the application, including service discovery.</p>\n<p>If we were to configure everything manually, it would look something like this:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddServiceDiscovery();\n\nbuilder.Services.AddHttpClient(&quot;weather-api&quot;, (_, client) =&gt; {\n    client.BaseAddress = new Uri(&quot;http://weather-api&quot;);\n})\n.AddServiceDiscovery();\n</code></pre>\n<p>The first <code>AddServiceDiscovery()</code> method registers the necessary services to enable service discovery in the application.\nThe second <code>AddServiceDiscovery()</code> method on the HTTP client configures it to use service discovery for resolving the base address.\nThis means that when the HTTP client makes requests to <code>http://weather-api</code>,\nit will automatically resolve the correct endpoint based on the service discovery configuration.</p>\n<p>We can also configure service discovery globally for all HTTP clients in the application:</p>\n<pre><code class=\"language-csharp\">builder.Services.ConfigureHttpClientDefaults(static http =&gt;\n{\n    // Turn on service discovery by default\n    http.AddServiceDiscovery();\n});\n</code></pre>\n<p>This configuration ensures that all HTTP clients in the application will use service discovery by default,\neliminating the need to configure it for each client individually.</p>\n<p>What Aspire does at runtime for all of this to work is inject a set of configuration values.\nThe configuration value names are derived from the service names we defined in the App Host, plus the respective scheme (http or https).\nHere's an example of what the <code>weather-api</code> configuration might look like:</p>\n<pre><code class=\"language-json\">{\n  &quot;Services&quot;: {\n    &quot;weather-api&quot;: {\n      &quot;http&quot;: [\n        &quot;localhost:8080&quot;\n      ]\n    }\n  }\n}\n</code></pre>\n<p>We can also configure service discovery to work with HTTPS by adding the <code>https</code> scheme to the service name:</p>\n<pre><code class=\"language-csharp\">// Specify the https scheme explicitly in the service name\nbuilder.Services.AddHttpClient(&quot;weather-api&quot;, (_, client) =&gt; {\n    client.BaseAddress = new Uri(&quot;https://weather-api&quot;);\n});\n\n// Alternatively, we can use the https+http scheme and let Aspire handle the conversion\nbuilder.Services.AddHttpClient(&quot;weather-api-2&quot;, (_, client) =&gt; {\n    client.BaseAddress = new Uri(&quot;https+http://weather-api&quot;);\n});\n</code></pre>\n<p>A significant advantage of Aspire's service discovery is its consistent behavior across environments:</p>\n<ul>\n<li>During development, services might run on localhost with different ports</li>\n<li>In testing environments, services could be containerized</li>\n<li>In production, services might be deployed to Kubernetes or other platforms</li>\n</ul>\n<p>Your code remains unchanged across these scenarios because the service name abstraction shields you from the underlying networking details.</p>\n<p>Note that you don't have to use .NET Aspire to benefit from <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/service-discovery\">service discovery</a>.\nIt's available as a standalone library (<code>Microsoft.Extensions.ServiceDiscovery</code>) and you can use it in any .NET application.</p>\n<h2>Service Discovery with YARP as a Proxy</h2>\n<p>A powerful application of .NET Aspire's service discovery capabilities is in API gateway scenarios using\n<a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>YARP</strong></a> (Yet Another Reverse Proxy).\nLet's explore how to implement this pattern:</p>\n<pre><code class=\"language-csharp\">// In the App Host\nvar apiService = builder.AddProject&lt;Projects.WeatherApi&gt;(&quot;weather-api&quot;);\nvar userService = builder.AddProject&lt;Projects.UserApi&gt;(&quot;user-api&quot;);\nvar proxyService = builder.AddProject&lt;Projects.ApiGateway&gt;(&quot;api-gateway&quot;)\n    .WithReference(apiService)\n    .WithReference(userService);\n</code></pre>\n<p>We'll need to add the YARP NuGet package to the API gateway project:</p>\n<pre><code class=\"language-powershell\">Install-Package Yarp.ReverseProxy # Adds the YARP package\nInstall-Package Microsoft.Extensions.ServiceDiscovery.Yarp # Adds the YARP service discovery package\n</code></pre>\n<p>In the gateway project, we can configure YARP to use service discovery:</p>\n<pre><code class=\"language-csharp\">// In Program.cs of the api-gateway project\nvar builder = WebApplication.CreateBuilder(args);\n\n// Cofigures the service discovery services\nbuilder.Services.AddServiceDiscovery();\n\n// Add YARP services\nbuilder.Services.AddReverseProxy()\n    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;))\n    // Configures a destination resolver that can use service discovery\n    .AddServiceDiscoveryDestinationResolver();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline\napp.MapReverseProxy();\n\napp.Run();\n</code></pre>\n<p>The YARP configuration in <code>appsettings.json</code> leverages the service names for endpoint resolution:</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;weather-route&quot;: {\n        &quot;ClusterId&quot;: &quot;weather-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/weather/{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [\n          { &quot;PathRemovePrefix&quot;: &quot;/weather&quot; }\n        ]\n      },\n      &quot;user-route&quot;: {\n        &quot;ClusterId&quot;: &quot;user-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/users/{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [\n          { &quot;PathRemovePrefix&quot;: &quot;/users&quot; }\n        ]\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;weather-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://weather-api&quot;\n          }\n        }\n      },\n      &quot;user-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://user-api&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<p>This configuration creates an API gateway that:</p>\n<ul>\n<li>Routes requests with path <code>/weather/*</code> to the weather API service</li>\n<li>Routes requests with path <code>/users/*</code> to the user API service</li>\n<li>Uses service names (<code>weather-api</code> and <code>user-api</code>) that service discovery resolves at runtime</li>\n</ul>\n<p>The beauty of this approach is that the gateway doesn't need to know the actual endpoints of the backend services.\nIt simply uses the service names, and .NET Aspire handles providing the configuration at runtime.\nThis makes the gateway configuration more portable and easier to maintain as the application evolves.</p>\n<h2>Conclusion</h2>\n<p>.NET Aspire transforms service discovery from a complex infrastructure challenge into a straightforward configuration concern.\nBy using a configuration-based approach rather than a centralized registry,\nit simplifies the development experience while maintaining the flexibility needed for various deployment scenarios.</p>\n<p>The key advantages of Aspire's service discovery include:</p>\n<ul>\n<li>Declarative service relationships in the App Host</li>\n<li>Simple service name resolution that works across environments</li>\n<li>Seamless integration with the .NET ecosystem and dependency injection</li>\n<li>Powerful applications in patterns like API gateways with YARP</li>\n</ul>\n<p>As the .NET Aspire stack continues to evolve, its approach to service discovery represents\none of the ways it's making cloud-native development more accessible to .NET developers.\nBy reducing the complexity of service-to-service communication,\nAspire enables teams to focus on building features rather than wrestling with infrastructure concerns.</p>\n<p>If you want to explore more robust service discovery solutions for large-scale distributed systems,\ncheck out my previous article on <a href=\"https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul\"><strong>implementing service discovery with Consul</strong></a>.\nIt provides a complementary approach for scenarios that might require a more traditional service registry.</p>\n<p>For those who found the YARP integration particularly interesting, my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>\ncourse dives deeper into building scalable applications with YARP as an API gateway.\nYou'll learn how to leverage these patterns to create maintainable,\nevolvable systems regardless of whether you're using microservices or a monolith.</p>\n<p>That's all for today.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery",
            "title": "How .NET Aspire Simplifies Service Discovery",
            "summary": ".NET Aspire revolutionizes distributed application development by simplifying service discovery through configuration-based approaches that eliminate the…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_135.png",
            "date_modified": "2025-03-29T00:00:00.000Z",
            "date_published": "2025-03-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/options-pattern-validation-in-aspnetcore-with-fluentvalidation",
            "content_html": "<p>You validate options with FluentValidation by implementing <code>IValidateOptions&lt;T&gt;</code> that resolves the matching validator from a scoped service provider and maps failures to <code>ValidateOptionsResult.Fail</code>.\nChain <code>ValidateOnStart()</code> when configuring the options, so a bad <code>appsettings.json</code> stops the application from starting instead of failing later at runtime.</p>\n<p>If you've worked with the <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7\"><strong>Options Pattern</strong></a> in ASP.NET Core,\nyou're likely familiar with the built-in validation using <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-9.0#validation-attributes\">Data Annotations</a>.\nWhile functional, Data Annotations can be limiting for complex validation scenarios.</p>\n<p>The <strong>Options Pattern</strong> lets you use classes to obtain strongly typed configuration objects at runtime.</p>\n<p>The problem? You can't be certain that the configuration is valid until you try to use it.</p>\n<p>So why not validate it at application startup?</p>\n<p>In this article, we'll explore how to integrate the more powerful <a href=\"https://docs.fluentvalidation.net/en/latest/\">FluentValidation</a>\nlibrary with ASP.NET Core's <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options\">Options Pattern</a>,\nto build a robust validation solution that executes at application startup.</p>\n<h2>Why FluentValidation Over Data Annotations?</h2>\n<p>Data Annotations work well for simple validations, but FluentValidation offers several advantages:</p>\n<ul>\n<li>More expressive and flexible validation rules</li>\n<li>Better support for complex conditional validations</li>\n<li>Cleaner separation of concerns (validation logic separate from model)</li>\n<li>Easier testing of validation rules</li>\n<li>Better support for custom validation logic</li>\n<li>Allows for injecting dependencies into validators</li>\n</ul>\n<h2>Understanding the Options Pattern Lifecycle</h2>\n<p>Before diving deep into validation, it's important to understand the lifecycle of options in ASP.NET Core:</p>\n<ul>\n<li>Options are registered with the DI container</li>\n<li>Configuration values are bound to options classes</li>\n<li>Validation occurs (if configured)</li>\n<li>Options are resolved when requested via <code>IOptions&lt;T&gt;</code>, <code>IOptionsSnapshot&lt;T&gt;</code>, or <code>IOptionsMonitor&lt;T&gt;</code></li>\n</ul>\n<p>The <code>ValidateOnStart()</code> method forces validation to occur during application startup rather than when options are first resolved.</p>\n<h2>Common Configuration Failures Without Validation</h2>\n<p>Without validation, configuration issues can manifest in several ways:</p>\n<ul>\n<li><strong>Silent failures</strong>: An incorrectly configured option may result in default values being used without warning</li>\n<li><strong>Runtime exceptions</strong>: Configuration issues may only surface when the application tries to use invalid values</li>\n<li><strong>Cascading failures</strong>: One misconfigured component can cause failures in dependent systems</li>\n</ul>\n<p>By validating at startup, you create a fast feedback loop that prevents these issues.</p>\n<h2>Setting Up the Foundation</h2>\n<p>First, let's add the FluentValidation package to our project:</p>\n<pre><code class=\"language-powershell\">Install-Package FluentValidation # base package\nInstall-Package FluentValidation.DependencyInjectionExtensions # for DI integration\n</code></pre>\n<p>For our example, we'll use a <code>GitHubSettings</code> class that requires validation:</p>\n<pre><code class=\"language-csharp\">public class GitHubSettings\n{\n    public const string ConfigurationSection = &quot;GitHubSettings&quot;;\n\n    public string BaseUrl { get;init; }\n    public string AccessToken { get; init; }\n    public string RepositoryName { get; init; }\n}\n</code></pre>\n<h2>Creating a FluentValidation Validator</h2>\n<p>Next, we'll create a validator for our settings class:</p>\n<pre><code class=\"language-csharp\">public class GitHubSettingsValidator : AbstractValidator&lt;GitHubSettings&gt;\n{\n    public GitHubSettingsValidator()\n    {\n        RuleFor(x =&gt; x.BaseUrl).NotEmpty();\n\n        RuleFor(x =&gt; x.BaseUrl)\n            .Must(baseUrl =&gt; Uri.TryCreate(BaseUrl, UriKind.Absolute, out _))\n            .When(x =&gt; !string.IsNullOrWhiteSpace(x.baseUrl))\n            .WithMessage($&quot;{nameof(GitHubSettings.BaseUrl)} must be a valid URL&quot;);\n\n        RuleFor(x =&gt; x.AccessToken)\n            .NotEmpty();\n\n        RuleFor(x =&gt; x.RepositoryName)\n            .NotEmpty();\n    }\n}\n</code></pre>\n<h2>Building the FluentValidation Integration</h2>\n<p>To integrate FluentValidation with the Options Pattern, we need to create a custom <code>IValidateOptions&lt;T&gt;</code> implementation:</p>\n<pre><code class=\"language-csharp\">using FluentValidation;\nusing Microsoft.Extensions.Options;\n\npublic class FluentValidateOptions&lt;TOptions&gt;\n    : IValidateOptions&lt;TOptions&gt;\n    where TOptions : class\n{\n    private readonly IServiceProvider _serviceProvider;\n    private readonly string? _name;\n\n    public FluentValidateOptions(IServiceProvider serviceProvider, string? name)\n    {\n        _serviceProvider = serviceProvider;\n        _name = name;\n    }\n\n    public ValidateOptionsResult Validate(string? name, TOptions options)\n    {\n        if (_name is not null &amp;&amp; _name != name)\n        {\n            return ValidateOptionsResult.Skip;\n        }\n\n        ArgumentNullException.ThrowIfNull(options);\n\n        using var scope = _serviceProvider.CreateScope();\n\n        var validator = scope.ServiceProvider.GetRequiredService&lt;IValidator&lt;TOptions&gt;&gt;();\n\n        var result = validator.Validate(options);\n        if (result.IsValid)\n        {\n            return ValidateOptionsResult.Success;\n        }\n\n        var type = options.GetType().Name;\n        var errors = new List&lt;string&gt;();\n\n        foreach (var failure in result.Errors)\n        {\n            errors.Add($&quot;Validation failed for {type}.{failure.PropertyName} &quot; +\n                       $&quot;with the error: {failure.ErrorMessage}&quot;);\n        }\n\n        return ValidateOptionsResult.Fail(errors);\n    }\n}\n</code></pre>\n<p>A few important notes about this implementation:</p>\n<ol>\n<li>We create a scoped service provider to properly resolve the validator (since validators are typically registered as scoped services)</li>\n<li>We handle named options through the <code>_name</code> property</li>\n<li>We build informative error messages that include the property name and error message</li>\n</ol>\n<h2>How the FluentValidation Integration Works</h2>\n<p>When adding our custom FluentValidation integration, it's helpful to understand how it connects to ASP.NET Core's options system:</p>\n<ol>\n<li>The <code>IValidateOptions&lt;T&gt;</code> interface is the hook that ASP.NET Core provides for options validation</li>\n<li>Our <code>FluentValidateOptions&lt;T&gt;</code> class implements this interface to bridge to FluentValidation</li>\n<li>When <code>ValidateOnStart()</code> is called, ASP.NET Core resolves all <code>IValidateOptions&lt;T&gt;</code> implementations and runs them</li>\n<li>If validation fails, an <code>OptionsValidationException</code> is thrown, preventing the application from starting</li>\n</ol>\n<h2>Creating Extension Methods for Easy Integration</h2>\n<p>Now, let's create a few extension methods to make our validation easier to use:</p>\n<pre><code class=\"language-csharp\">public static class OptionsBuilderExtensions\n{\n    public static OptionsBuilder&lt;TOptions&gt; ValidateFluentValidation&lt;TOptions&gt;(\n        this OptionsBuilder&lt;TOptions&gt; builder)\n        where TOptions : class\n    {\n        builder.Services.AddSingleton&lt;IValidateOptions&lt;TOptions&gt;&gt;(\n            serviceProvider =&gt; new FluentValidateOptions&lt;TOptions&gt;(\n                serviceProvider,\n                builder.Name));\n\n        return builder;\n    }\n}\n</code></pre>\n<p>This extension method allows us to call <code>.ValidateFluentValidation()</code> when configuring options, similar to the built-in <code>.ValidateDataAnnotations()</code> method.</p>\n<p>For even more convenience, we can create another extension method to simplify the entire configuration process:</p>\n<pre><code class=\"language-csharp\">public static class ServiceCollectionExtensions\n{\n    public static IServiceCollection AddOptionsWithFluentValidation&lt;TOptions&gt;(\n        this IServiceCollection services,\n        string configurationSection)\n        where TOptions : class\n    {\n        services.AddOptions&lt;TOptions&gt;()\n            .BindConfiguration(configurationSection)\n            .ValidateFluentValidation() // Configure FluentValidation validation\n            .ValidateOnStart(); // Validate options on application start\n\n        return services;\n    }\n}\n</code></pre>\n<h2>Registering and Using the Validation</h2>\n<p>There are a few ways to use our FluentValidation integration:</p>\n<h3>Option 1: Standard Registration with Manual Validator Registration</h3>\n<pre><code class=\"language-csharp\">// Register the validator\nbuilder.Services.AddScoped&lt;IValidator&lt;GitHubSettings&gt;, GitHubSettingsValidator&gt;();\n\n// Configure options with validation\nbuilder.Services.AddOptions&lt;GitHubSettings&gt;()\n    .BindConfiguration(GitHubSettings.ConfigurationSection)\n    .ValidateFluentValidation() // Configure FluentValidation validation\n    .ValidateOnStart();\n</code></pre>\n<h3>Option 2: Using the Convenience Extension Method</h3>\n<pre><code class=\"language-csharp\">// Register the validator\nbuilder.Services.AddScoped&lt;IValidator&lt;GitHubSettings&gt;, GitHubSettingsValidator&gt;();\n\n// Use the convenience extension\nbuilder.Services.AddOptionsWithFluentValidation&lt;GitHubSettings&gt;(GitHubSettings.ConfigurationSection);\n</code></pre>\n<h3>Option 3: Automatic Validator Registration</h3>\n<p>If you have many validators and want to register them all at once, you can use FluentValidation's assembly scanning:</p>\n<pre><code class=\"language-csharp\">// Register all validators from assembly\nbuilder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);\n\n// Use the convenience extension\nbuilder.Services.AddOptionsWithFluentValidation&lt;GitHubSettings&gt;(GitHubSettings.ConfigurationSection);\n</code></pre>\n<h2>What Happens at Runtime?</h2>\n<p>With <code>.ValidateOnStart()</code>, the application will throw an exception during startup if any validation rules fail.\nFor example, if your <code>appsettings.json</code> is missing the required <code>AccessToken</code>, you'll see something like:</p>\n<pre><code>Microsoft.Extensions.Options.OptionsValidationException:\n    Validation failed for GitHubSettings.AccessToken with the error: 'Access Token' must not be empty.\n</code></pre>\n<p>This prevents your application from even starting with invalid configuration, ensuring issues are caught as early as possible.</p>\n<h2>Working with Different Configuration Sources</h2>\n<p>ASP.NET Core's configuration system supports multiple sources.\nWhen using the Options Pattern with FluentValidation, remember that validation works regardless of the source:</p>\n<ul>\n<li>Environment variables</li>\n<li>Azure Key Vault</li>\n<li>User secrets</li>\n<li>JSON files</li>\n<li>In-memory configuration</li>\n</ul>\n<p>This is particularly useful for containerized applications where configuration comes from environment variables or mounted secrets.</p>\n<h2>Testing Your Validators</h2>\n<p>One benefit of using FluentValidation is that validators are easy to test:</p>\n<pre><code class=\"language-csharp\">// Uses helper methods from FluentValidation.TestHelper\n[Fact]\npublic void GitHubSettings_WithMissingAccessToken_ShouldHaveValidationError()\n{\n    // Arrange\n    var validator = new GitHubSettingsValidator();\n    var settings = new GitHubSettings { RepositoryName = &quot;test-repo&quot; };\n\n    // Act\n    TestValidationResult&lt;GitHubSettings&gt;? result = await validator.TestValidate(settings);\n\n    // Assert\n    result.ShouldHaveValidationErrorFor(x =&gt; x.BaseUrl);\n    result.ShouldHaveValidationErrorFor(x =&gt; x.AccessToken);\n}\n</code></pre>\n<h2>Summary</h2>\n<p>By combining FluentValidation with the Options Pattern and <code>ValidateOnStart()</code>,\nwe create a powerful <a href=\"https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation\"><strong>validation system</strong></a> that ensures our application has correct configuration at startup.</p>\n<p>This approach:</p>\n<ol>\n<li>Provides more expressive validation rules than Data Annotations</li>\n<li>Separates validation logic from configuration models</li>\n<li>Catches configuration errors at application startup</li>\n<li>Supports complex validation scenarios</li>\n<li>Is easily testable</li>\n</ol>\n<p>This pattern is particularly valuable in <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>microservice architectures</strong></a> or containerized applications\nwhere configuration errors should be detected immediately rather than at runtime.</p>\n<p>Remember to register your validators appropriately and use <code>.ValidateOnStart()</code> to ensure validation happens during application startup.</p>\n<p>That's all for today.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/options-pattern-validation-in-aspnetcore-with-fluentvalidation",
            "title": "Options Pattern Validation in ASP.NET Core With FluentValidation",
            "summary": "Elevate your ASP.NET Core configuration with FluentValidation integration that catches configuration errors at startup, preventing silent failures and runtime…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_134.png",
            "date_modified": "2025-03-22T00:00:00.000Z",
            "date_published": "2025-03-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/streamlining-dotnet-9-deployment-with-github-actions-and-azure",
            "content_html": "<p>Deploying a .NET 9 app to Azure with GitHub Actions takes two jobs.\nThe first restores, builds, tests, and publishes the app, then uploads the output as an artifact.\nThe second downloads that artifact and pushes it to Azure App Service with <code>azure/webapps-deploy</code>, authenticating with a publish profile stored as a GitHub secret.</p>\n<p>I remember the days of deploying .NET applications by hand: publishing locally, copying files to servers, running scripts,\nand crossing my fingers that nothing would break.\nIt was stressful, time-consuming, and honestly, a bit scary.</p>\n<p>But those days are over.</p>\n<p>After implementing <a href=\"https://en.wikipedia.org/wiki/CI/CD\">CI/CD</a> pipelines for dozens of projects,\nI've seen firsthand how automation transforms the deployment process from a dreaded chore into a reliable, even boring, part of development.</p>\n<p>And boring deployments are good deployments.</p>\n<p>In this article, I'll walk you through setting up a robust CI/CD pipeline for .NET 9 applications using\n<a href=\"https://github.com/features/actions\">GitHub Actions</a> and <a href=\"https://azure.microsoft.com/en-us/products/app-service\">Azure App Service</a>.\nI'll cover:</p>\n<ul>\n<li>What CI/CD is and why it matters for .NET developers</li>\n<li>A complete workflow that builds, tests, and deploys your application</li>\n<li>How to extend your pipeline with database migrations, code coverage, and more</li>\n<li>Practical tips I've learned from real-world deployments</li>\n</ul>\n<p>Whether you're tired of manual deployments or looking to improve your existing automation,\nthis guide will help you build a robust CI/CD pipeline that you can easily extend to fit your needs.</p>\n<h2>What is CI/CD and Why Should You Care?</h2>\n<p>CI/CD stands for <strong>Continuous Integration</strong> and <strong>Continuous Delivery/Deployment</strong>.</p>\n<p>In simple terms:</p>\n<ul>\n<li><strong>Continuous Integration (CI)</strong> means frequently merging code changes and running automated tests to catch issues early</li>\n<li><strong>Continuous Delivery (CD)</strong> means getting those changes to production-ready environments quickly and safely</li>\n<li><strong>Continuous Deployment (CD)</strong> is an extension of Continuous Delivery where every change that passes automated tests is deployed to production automatically</li>\n</ul>\n<p>The main benefits I've seen:</p>\n<ol>\n<li><strong>Faster feedback</strong>: Find bugs within minutes instead of days</li>\n<li><strong>More stable releases</strong>: Small, incremental changes are easier to fix</li>\n<li><strong>Time savings</strong>: Let automation handle repetitive tasks while you focus on writing code</li>\n<li><strong>Consistent deployment</strong>: No more &quot;it works on my machine&quot; problems</li>\n</ol>\n<h2>My GitHub Actions Workflow for .NET 9</h2>\n<p>Here's the workflow I use to deploy a simple time service API to Azure App Service:</p>\n<pre><code class=\"language-yaml\"># Name of the workflow as it appears in GitHub Actions UI\nname: Time Service CI\n\n# Define when this workflow will run\non:\n  workflow_dispatch: # Allow manual triggering from GitHub UI\n  push:\n    branches:\n      - main # Run automatically when code is pushed to main branch\n\n# Environment variables used throughout the workflow\nenv:\n  AZURE_WEBAPP_NAME: time-service\n  AZURE_WEBAPP_PACKAGE_PATH: './Time.Api/publish'\n  DOTNET_VERSION: '9.x'\n  SOLUTION_PATH: 'Time.Api.sln'\n  API_PROJECT_PATH: 'Time.Api'\n  PUBLISH_DIR: './publish'\n\n# Define the separate jobs that make up this workflow\njobs:\n  # First job: build and test the application\n  build-and-test:\n    name: Build and Test\n    runs-on: ubuntu-latest # Use Ubuntu runner for this job\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: ${{ env.DOTNET_VERSION }}\n\n      - name: Restore\n        run: dotnet restore ${{ env.SOLUTION_PATH }}\n\n      - name: Build\n      run: dotnet build ${{ env.SOLUTION_PATH }}\n        --configuration Release\n        --no-restore\n\n    - name: Test\n      run: dotnet test ${{ env.SOLUTION_PATH }}\n        --configuration Release\n        --no-restore\n        --no-build\n        --verbosity normal\n\n\n    - name: Publish\n      run: dotnet publish ${{ env.API_PROJECT_PATH }}\n        --configuration Release\n        --no-restore\n        --no-build\n        --property:PublishDir=${{ env.PUBLISH_DIR }}\n\n    # Store the published output as an artifact for later jobs\n    - name: Publish Artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: webapp  # Name of the artifact\n        path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}\n\n  # Second job: deploy the application to Azure\n  deploy:\n    name: Deploy to Azure\n    runs-on: ubuntu-latest\n    needs: [build-and-test] # This job depends on the build-and-test job\n\n    steps:\n      # Retrieve the artifacts from the build job\n      - name: Download artifact from build job\n        uses: actions/download-artifact@v4\n        with:\n          name: webapp\n          path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}\n\n      # Deploy to Azure App Service using publish profile credentials\n      - name: Deploy\n        uses: azure/webapps-deploy@v2\n        with:\n          app-name: ${{ env.AZURE_WEBAPP_NAME }}\n          # Authentication credentials stored as a secret\n          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}\n          package: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'\n</code></pre>\n<p>This workflow does two main things: it builds and tests the code and then deploys it to Azure.</p>\n<p>The first job checks out our repository, sets up .NET 9, and runs through a standard build process:\nrestore packages, build the solution, run tests, and publish the application.\nThe detailed comments in the YAML explain each step.\nOnce everything passes, it packages the application as an artifact for the next job.</p>\n<p>The second job takes that artifact and deploys it to Azure App Service using a publish profile.\nI store the publish profile as a <a href=\"https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions\">GitHub secret</a> for security.\nThe <code>needs: [build-and-test]</code> line ensures deployment only happens if all tests pass, which protects our production environment from broken code.</p>\n<figure className=\"figure-center\">\n  <div className=\"bordered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_133/ci_cd_pipeline.png\" alt=\"An example of what a workflow run looks like from the GitHub UI.\">\n  </div>\n  <figcaption>\n    Here's an example of what a workflow run looks like from the GitHub UI.\n  </figcaption>\n</figure>\n<h2>Extending Your CI/CD Pipeline</h2>\n<p>While the basic workflow gets your application deployed, most real-world projects need more sophisticated pipelines.\nAs your project grows, so should your CI/CD process.\nExtensions to your pipeline could help catch issues earlier, ensure quality standards, and provide better visibility into your development process.</p>\n<p>Here are some valuable additions to consider:</p>\n<h3>1. Running Database Migrations</h3>\n<p>Database schema changes can be tricky to coordinate with code deployments.\nThere are several approaches to handling this:</p>\n<p><strong>Using EF Core Migration Bundles</strong>:</p>\n<pre><code class=\"language-yaml\">- name: Create migration bundle\n  run: dotnet ef migrations bundle --project ${{ env.DATA_PROJECT }} --output ${{ env.MIGRATIONS_BUNDLE }}\n\n- name: Apply migrations\n  run: ${{ env.MIGRATIONS_BUNDLE }}\n</code></pre>\n<p><a href=\"https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide\"><strong>Migration bundles</strong></a> (introduced in EF Core 6.0) package your migrations into a standalone executable,\nmaking them easier to run in deployment pipelines.</p>\n<p><strong>Adding Manual Review for Migrations</strong>:</p>\n<pre><code class=\"language-yaml\">deploy-database:\n  name: Deploy Database Changes\n  environment: production\n  runs-on: ubuntu-latest\n  needs: [build-and-test]\n</code></pre>\n<p>This approach adds an environment with protection rules, requiring a DBA to review and approve migration scripts before they run.\nThis is safer for production databases with valuable data.</p>\n<p><strong>Pros</strong>:</p>\n<ul>\n<li>No manual migration steps</li>\n<li>Schema and code changes deploy together</li>\n<li>Database changes are versioned with code</li>\n</ul>\n<p><strong>Cons</strong>:</p>\n<ul>\n<li>Failed migrations can be hard to roll back</li>\n<li>Might need extra handling for production data</li>\n<li>Requires secure database credentials in CI</li>\n</ul>\n<p>To minimize risks, I test migrations in a staging environment first and always back up production databases before deployment.</p>\n<h3>2. Code Coverage Reports</h3>\n<p>I like knowing how much of my code is covered by tests.\nHere's an example of how to generate and publish code coverage reports to <a href=\"https://about.codecov.io/\">Codecov</a>:</p>\n<pre><code class=\"language-yaml\">- name: Generate coverage report\n  run: dotnet test ${{ env.SOLUTION_PATH }} /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura\n\n- name: Publish coverage report\n  uses: codecov/codecov-action@v5\n  with:\n    files: ./**/coverage.cobertura.xml\n    fail_ci_if_error: true\n    token: ${{ secrets.CODECOV_TOKEN }}\n</code></pre>\n<p>Adding minimum coverage requirements prevents drops in test coverage and encourages the team to maintain quality standards.\nYou can also configure it to fail builds when coverage falls below a threshold.</p>\n<h3>3. Multi-Environment Deployment</h3>\n<p>For larger projects, deploying to multiple environments with approval gates provides better control:</p>\n<pre><code class=\"language-yaml\">deploy-staging:\n  name: Deploy to Staging\n  environment: staging\n  runs-on: ubuntu-latest\n  needs: [build-and-test]\n  steps:\n    # Deployment steps...\n\ndeploy-production:\n  name: Deploy to Production\n  environment: production\n  runs-on: ubuntu-latest\n  needs: [deploy-staging]\n  steps:\n    # Deployment steps...\n</code></pre>\n<p>Adding protection rules to your production environment creates checkpoints where team members can verify changes before they reach users.</p>\n<p>Here's an example of some GitHub Environment protection rules:</p>\n<ul>\n<li><strong>Required reviewers</strong>: Specify team members who must approve deployments</li>\n<li><strong>Wait timers</strong>: Add a delay before deployments to give time for review</li>\n<li><strong>Deployment branches</strong>: Restrict which branches can deploy to production</li>\n</ul>\n<p>These guardrails are especially important for critical environments where downtime can be costly.</p>\n<h2>Final Thoughts</h2>\n<p>A good CI/CD pipeline evolves with your project.\nStart simple, focus on automating the most painful manual tasks first, then gradually add more features as needed.</p>\n<p>The initial setup takes time, but the long-term benefits are huge.\nMy team now deploys multiple times per day instead of once every few weeks, with fewer bugs reaching production.</p>\n<p>If you want to learn more about building robust APIs that complement your CI/CD process, check out my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a> course.\nIt covers designing, implementing, and deploying production-ready APIs with best practices that work perfectly with the deployment pipeline we've discussed here.</p>\n<p>What's your CI/CD setup like?\nI'd love to hear how you've customized your workflows for .NET applications.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/streamlining-dotnet-9-deployment-with-github-actions-and-azure",
            "title": "Streamlining .NET 9 Deployment With GitHub Actions and Azure",
            "summary": "Build a robust, automated CI/CD pipeline for .NET 9 applications using GitHub Actions and Azure App Service to transform deployments from stressful events into…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_133.png",
            "date_modified": "2025-03-15T00:00:00.000Z",
            "date_published": "2025-03-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/better-request-tracing-with-user-context-in-asp-net-core",
            "content_html": "<p>To add user context to request tracing in ASP.NET Core, write a middleware that reads the user ID from the authenticated user's claims, tags the current <code>Activity</code>, and opens a logging scope containing the ID.\nEvery log entry and trace in the request then carries the user ID.\nRegister the middleware after authentication and authorization so the user identity is available.</p>\n<p>When building web applications, knowing what's happening behind the scenes is crucial.\nIn ASP.NET Core, we can make our lives easier by adding user context to our request tracing.\nThis helps us track issues, understand user behavior, and improve our applications.</p>\n<p>Let me show you how to enhance your request tracing by adding user context in ASP.NET Core applications.</p>\n<h2>Why Add User Context to Request Tracing?</h2>\n<p>When something goes wrong in your application, having the user's ID in your logs makes it much easier to figure out what happened.\nInstead of searching through thousands of log entries, you can filter by user ID and see only the relevant logs.</p>\n<p>Adding user context also helps with:</p>\n<ul>\n<li>Tracking user journeys through your application</li>\n<li>Identifying patterns in user behavior</li>\n<li>Troubleshooting issues for specific users</li>\n<li>Monitoring performance for different user segments</li>\n</ul>\n<h2>Implementing User Context Enrichment</h2>\n<p>The core of our solution is a middleware component that extracts the user ID from the current user's claims and adds it to the current <strong>activity</strong> and <strong>logging scope</strong>.</p>\n<p>A <strong>logging scope</strong> in ASP.NET Core lets you attach additional data to all log messages created within that scope.\n<a href=\"https://milanjovanovic.tech/blog/structured-logging-in-asp-net-core-with-serilog\"><strong>Structured logging</strong></a> frameworks like <a href=\"https://milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging\"><strong>Serilog</strong></a>\nand the built-in logger support this feature.\nFor example, if you add a user ID to a logging scope, every log message within that scope will include that user ID,\neven if the log message itself doesn't mention the user.\nThis makes it easy to <strong>correlate logs</strong> for a specific user across different parts of your application.</p>\n<p>The <code>Activity</code> class is part of the .NET diagnostics infrastructure.\nIt represents a unit of work or operation and is designed for distributed tracing across service boundaries.\nWhen you add a tag to <code>Activity.Current</code>, that information becomes part of the trace and can be used to filter and analyze requests in your monitoring systems.</p>\n<p>Here's the code:</p>\n<pre><code class=\"language-csharp\">using System.Diagnostics;\nusing System.Security.Claims;\n\nnamespace MyApp.Middleware;\n\npublic sealed class UserContextEnrichmentMiddleware(\n    RequestDelegate next,\n    ILogger&lt;UserContextEnrichmentMiddleware&gt; logger)\n{\n    public async Task InvokeAsync(HttpContext context)\n    {\n        string? userId = context.User?.FindFirstValue(ClaimTypes.NameIdentifier);\n        if (userId is not null)\n        {\n            Activity.Current?.SetTag(&quot;user.id&quot;, userId);\n\n            var data = new Dictionary&lt;string, object&gt;\n            {\n                [&quot;UserId&quot;] = userId\n            };\n\n            using (logger.BeginScope(data))\n            {\n                await next(context);\n            }\n        }\n        else\n        {\n            await next(context);\n        }\n    }\n}\n</code></pre>\n<p>Let's break down what this middleware does:</p>\n<ol>\n<li>It extracts the user ID from the authenticated user's claims using <code>context.User?.FindFirstValue(ClaimTypes.NameIdentifier)</code></li>\n<li>If a user ID is found, it adds the ID as a tag to the current activity with <code>Activity.Current?.SetTag(&quot;user.id&quot;, userId)</code></li>\n<li>It creates a logging scope using <code>ILogger.BeginScope</code> with the user ID, which means all log entries within this scope will include the user ID</li>\n<li>It calls the next middleware in the pipeline</li>\n<li>If no user ID is found (for anonymous requests), it simply calls the next middleware</li>\n</ol>\n<h2>Logging Scopes and OpenTelemetry</h2>\n<p>If you're using <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>OpenTelemetry</strong></a> to export logs,\nyou have to configure the provider to include the log scopes on the generated log records.</p>\n<p>Here's how:</p>\n<pre><code class=\"language-csharp\">builder.Logging.AddOpenTelemetry(options =&gt;\n{\n    options.IncludeScopes = true;\n    options.IncludeFormattedMessage = true;\n});\n</code></pre>\n<h2>Adding the Middleware to Your Application</h2>\n<p>To use this <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\"><strong>middleware</strong></a> in your ASP.NET Core application, add it to your pipeline in the <code>Program.cs</code> file:</p>\n<pre><code class=\"language-csharp\">app.UseAuthentication();\napp.UseAuthorization();\n\n// Add the user context enrichment middleware after authentication\napp.UseMiddleware&lt;UserContextEnrichmentMiddleware&gt;();\n\napp.MapControllers();\n\napp.Run();\n</code></pre>\n<p>Make sure to place it after the authentication and authorization middleware so that the user identity is available.</p>\n<h2>Handling PII Concerns</h2>\n<p>When adding user IDs to your logs and traces, you need to be careful about <a href=\"https://en.wikipedia.org/wiki/Personal_data\">Personally Identifiable Information</a> (PII).\nHere are some important points to consider:</p>\n<ul>\n<li>User IDs should be opaque identifiers (like GUIDs) that don't reveal personal information</li>\n<li>Avoid logging email addresses, names, or other personal data</li>\n<li>Make sure your logging configuration doesn't send PII to systems where it shouldn't go</li>\n</ul>\n<p>If you need to comply with data protection regulations, consider implementing log retention policies and the ability to purge user data from logs when needed.</p>\n<h2>Expanding Context Enrichment</h2>\n<p>User IDs are just the beginning.\nWe can <strong>add more context</strong> to make our logs and traces even more useful:</p>\n<h3>Feature Flags</h3>\n<p><a href=\"https://milanjovanovic.tech/blog/feature-flags-in-dotnet-and-how-i-use-them-for-ab-testing\"><strong>Feature flags</strong></a> help us roll out new features gradually or enable them for specific users.\nAdding feature flag information to our context gives us valuable insights:</p>\n<pre><code class=\"language-csharp\">// Inside the middleware\nif (featureFlagService.IsEnabled(&quot;NewFeature&quot;, userId))\n{\n    Activity.Current?.SetTag(&quot;features.newfeature&quot;, &quot;enabled&quot;);\n    // Add to logging scope as well\n}\n</code></pre>\n<h3>Tenant Information for Multi-tenant Applications</h3>\n<p>If your application serves <a href=\"https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core\"><strong>multiple tenants</strong></a>, adding tenant IDs is extremely helpful:</p>\n<pre><code class=\"language-csharp\">string? tenantId = context.User?.FindFirstValue(&quot;TenantId&quot;);\nif (tenantId is not null)\n{\n    Activity.Current?.SetTag(&quot;tenant.id&quot;, tenantId);\n    // Add to logging scope\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Adding user context to your request tracing in ASP.NET Core is a simple but powerful technique.\nBy implementing the middleware we've explored, you'll see several important benefits:</p>\n<ol>\n<li>Faster troubleshooting - when users report issues, you can quickly find relevant logs</li>\n<li>Better understanding of usage patterns - see which features are being used and by whom</li>\n<li>Improved performance monitoring - identify slow requests for specific user segments</li>\n<li>More effective A/B testing - track metrics for users with different feature flags</li>\n</ol>\n<p>Understanding logging scopes and the <code>Activity</code> class helps you get the most out of this technique.\nLogging scopes ensure your log entries contain consistent contextual information,\nwhile activities enable distributed tracing across service boundaries.\nRemember to be careful with PII and make sure your logging practices comply with relevant regulations.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/better-request-tracing-with-user-context-in-asp-net-core",
            "title": "Better Request Tracing with User Context in ASP.NET Core",
            "summary": "Adding user context to request tracing in ASP.NET Core helps track issues and understand user behavior.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_132.png",
            "date_modified": "2025-03-08T00:00:00.000Z",
            "date_published": "2025-03-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/introduction-to-dapr-for-dotnet-developers",
            "content_html": "<p>Dapr (Distributed Application Runtime) is a CNCF runtime that runs as a sidecar next to your service and exposes building blocks for state, pub/sub, service invocation, secrets, and more.\nYour code calls Dapr over HTTP or gRPC, and the backing provider is swapped in a YAML component file instead of in code.</p>\n<p>Building distributed systems has never been more important—or more challenging.\nAs .NET developers, we're constantly juggling <a href=\"https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul\">service discovery</a>,\nstate management,\n<a href=\"https://milanjovanovic.tech/blog/simple-messaging-in-dotnet-with-redis-pubsub\">messaging</a>,\n<a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\">resilience patterns</a>,\nand various infrastructure SDKs.\nOur business logic gets buried under mountains of plumbing code, and we become tightly coupled to specific technologies.</p>\n<p>What if there was a better way?</p>\n<p>Enter <a href=\"https://dapr.io/\">Dapr</a> (Distributed Application Runtime), an open-source project that handles the complex infrastructure challenges so you can focus on what matters most:\nyour application's business logic.</p>\n<p>In this article, we'll explore how Dapr transforms microservice development for .NET developers by:</p>\n<ul>\n<li>Abstracting away infrastructure-specific code behind consistent APIs</li>\n<li>Providing standardized building blocks for common distributed system patterns</li>\n<li>Enabling you to use the same code from local development to production</li>\n<li>Integrating seamlessly with .NET and ASP.NET Core applications</li>\n</ul>\n<p>Whether you're building your <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>first microservice</strong></a> or evolving a complex system, Dapr offers a simpler path forward.\nLet's explore how it works.</p>\n<h2>What is Dapr?</h2>\n<p><a href=\"https://dapr.io/\">Dapr</a> is a portable, event-driven runtime that simplifies building microservices.\nAs a graduated project within the <a href=\"https://www.cncf.io/\">Cloud Native Computing Foundation</a> (CNCF), Dapr has proven its value in production environments.</p>\n<p>At its core, Dapr provides standardized building blocks that abstract away the complexity of common microservice patterns.\nRather than wrestling with infrastructure-specific code, you can focus on your business logic while Dapr handles the rest.</p>\n<p>Before Dapr, building a microservice architecture in .NET might require direct integration with multiple infrastructure components:</p>\n<pre><code class=\"language-csharp\">// Pre-Dapr approach - direct infrastructure dependencies\nbuilder.Services.AddStackExchangeRedisCache(options =&gt; { options.Configuration = &quot;redis:6379&quot;; });\n\nbuilder.Services.AddSingleton&lt;IMessageBroker&gt;(provider =&gt; new KafkaMessageBroker(&quot;kafka:9092&quot;));\n\nbuilder.Services.AddSingleton&lt;ISecretManager&gt;(provider =&gt;\n    new AzureKeyVaultClient(new Uri(&quot;https://myvault.vault.azure.net&quot;)));\n</code></pre>\n<p>This approach tightly couples your application to specific technologies.</p>\n<p>With Dapr, you gain flexibility through standardized APIs:</p>\n<pre><code class=\"language-csharp\">// Dapr approach - simple, consistent APIs\nbuilder.Services.AddDaprClient();\n\n// Later in code:\n// State management (could be Redis, Cosmos DB, etc.)\nawait daprClient.SaveStateAsync(&quot;statestore&quot;, &quot;customer-123&quot;, customerData);\n\n// Pub/sub (could be Kafka, RabbitMQ, etc.)\nawait daprClient.PublishEventAsync(&quot;pubsub&quot;, &quot;orders&quot;, orderData);\n\n// Secrets (could be Azure Key Vault, HashiCorp Vault, etc.)\nvar secret = await daprClient.GetSecretAsync(&quot;secretstore&quot;, &quot;api-keys&quot;);\n</code></pre>\n<p>The underlying providers can be swapped without code changes - just by updating Dapr component configuration files.</p>\n<h2>The Sidecar Pattern: How Dapr Works</h2>\n<p>Dapr uses the <a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/sidecar\">sidecar</a> architectural pattern,\nwhere it runs as a separate process alongside your application:</p>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_sidecar.png\" alt=\"Dapr sidecar diagram with the building blocks and services.\">\n  <figcaption>Source: <a href=\"https://dapr.io/\">Dapr</a></figcaption>\n</figure>\n<p>Your application communicates with the Dapr sidecar through HTTP or gRPC, and Dapr handles communication with infrastructure services.\nThis separation brings several benefits:</p>\n<ul>\n<li><strong>Language Agnostic</strong>: Dapr works with any programming language, including all .NET variants</li>\n<li><strong>Cross-cutting Concerns</strong>: Security, observability, and resiliency are handled by Dapr, not your code</li>\n<li><strong>Infrastructure Abstraction</strong>: Your application remains decoupled from specific technologies</li>\n<li><strong>Simplified Development</strong>: Clean, maintainable code focused on business logic</li>\n<li><strong>Production Ready</strong>: Built-in features that improve reliability in production environments</li>\n</ul>\n<h2>Building Blocks: Dapr's Core Capabilities</h2>\n<p>Dapr offers several <a href=\"https://docs.dapr.io/concepts/building-blocks-concept/\">building blocks</a> that solve common microservice challenges.\nEach provides a standardized API that abstracts away infrastructure complexity:</p>\n<ol>\n<li><strong>Service Invocation</strong>: Enables reliable service-to-service communication with automatic service discovery, load balancing, and retries.</li>\n<li><strong>State Management</strong>: Provides a unified way to store and retrieve state with features like concurrency control and transactions.</li>\n<li><strong>Pub/Sub</strong>: Implements asynchronous messaging between services, allowing for loosely-coupled, event-driven architectures.</li>\n<li><strong>Workflows</strong>: Enables you to define long running, persistent processes that span multiple microservices.</li>\n<li><strong>Bindings</strong>: Connects your applications to external systems, either for triggering your app from external events or invoking external services.</li>\n<li><strong>Actors</strong>: Implements the virtual actor pattern, making it easy to build stateful microservices with encapsulated state and behavior.</li>\n<li><strong>Secrets</strong>: Offers secure access to sensitive configuration like connection strings and API keys from various secret stores.</li>\n<li><strong>Configuration</strong>: Centralizes application settings with support for dynamic updates across multiple services.</li>\n<li><strong>Distributed Lock</strong>: Provides mutually exclusive access to shared resources in a distributed environment.</li>\n<li><strong>Cryptography</strong>: Offers encryption and decryption operations while handling key management.</li>\n<li><strong>Jobs</strong>: Allows you to schedule and orchestrate jobs (e.g., schedule batch processing jobs to run every business day)</li>\n<li><strong>Conversation</strong>: Lets you supply prompts to converse with different large language models (LLMs). Includes prompt caching and PII obfuscation.</li>\n</ol>\n<p>Here's an overview of Dapr's building blocks and the most popular services they interact with:</p>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_components.png\" alt=\"Dapr components diagram with the base building blocks and services.\">\n  <figcaption>Source: <a href=\"https://dapr.io/\">Dapr</a></figcaption>\n</figure>\n<p>Let's explore the most commonly used building blocks in depth.</p>\n<h2>Service Invocation</h2>\n<p>The <a href=\"https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/\">service invocation</a>\nbuilding block enables reliable service-to-service communication with automatic mTLS encryption, retries, and observability.</p>\n<p>This solves several challenging microservice problems:</p>\n<ul>\n<li><strong>Service Discovery</strong>: Finding where services are located</li>\n<li><strong>Resilient Communication</strong>: Handling transient failures gracefully</li>\n<li><strong>Load Balancing</strong>: Distributing requests across multiple instances</li>\n<li><strong>Observability</strong>: Tracking requests across service boundaries</li>\n<li><strong>Security</strong>: Encrypting traffic between services</li>\n</ul>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_service_invocation.png\" alt=\"Diagram showing how the service invocation flow looks like with Dapr.\">\n  <figcaption>Source: <a href=\"https://dapr.io/\">Dapr</a></figcaption>\n</figure>\n<p>Here's a simple example of invoking a service using Dapr's .NET SDK:</p>\n<pre><code class=\"language-csharp\">// Client application making a request\nusing Dapr.Client;\n\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddDaprClient();\n\nvar app = builder.Build();\n\napp.MapGet(&quot;/checkout/{itemId}&quot;, async (int itemId, DaprClient daprClient) =&gt;\n{\n    // Create order data\n    var orderData = new OrderData(itemId, DateTime.UtcNow);\n\n    // Invoke the order-processing service\n    var result = await daprClient.InvokeMethodAsync&lt;OrderData, string&gt;(\n        &quot;order-processor&quot;,\n        &quot;process-order&quot;,\n        orderData);\n\n    return Results.Ok(new { Message = $&quot;Order {itemId} processed: {result}&quot; });\n});\n\nawait app.RunAsync();\n\npublic record OrderData(int ItemId, DateTime OrderedAt);\n</code></pre>\n<p>And the corresponding service handling the request:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\nvar app = builder.Build();\n\napp.MapPost(&quot;/process-order&quot;, (OrderData order) =&gt;\n{\n    Console.WriteLine($&quot;Processing order {order.ItemId} placed at {order.OrderedAt}&quot;);\n    return $&quot;Order {order.ItemId} confirmation: #{Guid.NewGuid().ToString()[..8]}&quot;;\n});\n\nawait app.RunAsync();\n\npublic record OrderData(int ItemId, DateTime OrderedAt);\n</code></pre>\n<p>What's happening here:</p>\n<ul>\n<li>The <code>checkout</code> service calls <code>InvokeMethodAsync</code> using the <code>DaprClient</code> to send a request to the <code>order</code> service</li>\n<li>The Dapr sidecar for the checkout service receives this request</li>\n<li>The Dapr sidecar looks up the location of the order service</li>\n<li>The request is forwarded to the Dapr sidecar of the order service</li>\n<li>The order service's Dapr sidecar forwards the request to the order service</li>\n<li>The response follows the reverse path</li>\n</ul>\n<p>This process provides automatic service discovery, encryption, retries, and distributed tracing without any additional code.</p>\n<h2>Publish &amp; Subscribe</h2>\n<p>The <a href=\"https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/\">publish and subscribe</a>\nbuilding block provides asynchronous messaging between services with at-least-once delivery guarantees.\nThis pattern is essential for building resilient, loosely-coupled microservices that can:</p>\n<ul>\n<li>Process operations asynchronously without blocking the user</li>\n<li>Continue functioning when downstream services are unavailable</li>\n<li>Scale independently based on workload</li>\n</ul>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_publish_subscribe.png\" alt=\"Diagram showing how the publish subscribe flow looks like with Dapr.\">\n  <figcaption>Source: <a href=\"https://dapr.io/\">Dapr</a></figcaption>\n</figure>\n<p>Pub/Sub in Dapr follows this flow:</p>\n<ul>\n<li>A publisher service sends a message to a topic via the Dapr sidecar</li>\n<li>The publisher Dapr sidecar converts the message to the <a href=\"https://cloudevents.io/\">CloudEvent</a> format and forwards it to the configured message broker</li>\n<li>Subscriber services receive the message through their Dapr sidecars</li>\n<li>The subscriber application processes the message</li>\n</ul>\n<p>Dapr uses component configuration files to define the pub/sub message broker. Here's a typical Redis pub/sub component:</p>\n<pre><code class=\"language-yaml\">apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: order-events\nspec:\n  type: pubsub.redis\n  version: v1\n  metadata:\n    - name: redisHost\n      value: localhost:6379\n    - name: redisPassword\n      value: ''\n</code></pre>\n<p>The key parts of this configuration are:</p>\n<ul>\n<li><code>metadata.name</code>: The component name (<code>order-events</code>) that your application will reference when publishing/subscribing</li>\n<li><code>spec.type</code>: The type of component (<code>pubsub.redis</code> in this case)</li>\n<li><code>spec.metadata</code>: Configuration specific to the component type</li>\n</ul>\n<p>This file should be placed in a <code>components</code> directory where Dapr can discover it.\nWhen running locally, this is typically <code>./components/</code> relative to your application.</p>\n<p>What if you want to switch from Redis to RabbitMQ?\nYou'd replace the <code>spec.type</code> with <code>pubsub.rabbitmq</code> and update the <code>metadata</code> section accordingly.\nThis change doesn't require any code modifications in your application.\nIsn't this flexibility amazing?</p>\n<p>Here's how to publish events using Dapr:</p>\n<pre><code class=\"language-csharp\">// Publisher service\nusing Dapr.Client;\n\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddDaprClient();\n\nvar app = builder.Build();\n\napp.MapPost(&quot;/create-order&quot;, async (OrderRequest request, DaprClient daprClient) =&gt;\n{\n    var orderEvent = new OrderCreatedEvent(\n        request.OrderId,\n        request.CustomerId,\n        request.Items,\n        DateTime.UtcNow\n    );\n\n    // Publish event to &quot;orders&quot; topic\n    await daprClient.PublishEventAsync(&quot;order-events&quot;, &quot;orders&quot;, orderEvent);\n\n    return Results.Accepted();\n});\n\nawait app.RunAsync();\n\npublic record OrderRequest(Guid OrderId, string CustomerId, List&lt;string&gt; Items);\npublic record OrderCreatedEvent(Guid OrderId, string CustomerId, List&lt;string&gt; Items, DateTime CreatedAt);\n</code></pre>\n<p>And here's how a subscriber would handle these events:</p>\n<pre><code class=\"language-csharp\">// Subscriber service\nusing Dapr;\nusing Microsoft.AspNetCore.OutputCaching;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add Dapr event handling\nbuilder.Services.AddDapr();\nbuilder.Services.AddControllers();\n\nvar app = builder.Build();\n\n// Required for Dapr pub/sub\napp.UseCloudEvents();\napp.MapSubscribeHandler();\n\n// Subscribe to &quot;orders&quot; topic\napp.MapPost(&quot;/events/orders&quot;, [Topic(&quot;order-events&quot;, &quot;orders&quot;)] async (OrderCreatedEvent orderEvent) =&gt;\n{\n    Console.WriteLine($&quot;Processing order {orderEvent.OrderId} for customer {orderEvent.CustomerId}&quot;);\n    await ProcessOrderAsync(orderEvent);\n    return Results.Ok();\n});\n\nawait app.RunAsync();\n\nasync Task ProcessOrderAsync(OrderCreatedEvent orderEvent)\n{\n    // Process the order\n    await Task.Delay(100); // Simulate work\n}\n\npublic record OrderCreatedEvent(Guid OrderId, string CustomerId, List&lt;string&gt; Items, DateTime CreatedAt);\n</code></pre>\n<p>The key components:</p>\n<ul>\n<li>The publisher uses Dapr to send events to a topic</li>\n<li>Dapr handles the interaction with the message broker (Kafka, Redis, etc.)</li>\n<li>The subscriber decorates endpoints with <code>[Topic]</code> attributes</li>\n<li>Dapr delivers the messages to the appropriate subscribers</li>\n</ul>\n<p>Note that the <code>name</code> defined in the component file (<code>order-events</code> in our example) must match the first parameter used in\n<code>PublishEventAsync(&quot;order-events&quot;, ...)</code> and <code>[Topic(&quot;order-events&quot;, ...)]</code>.\nIf these names don't match exactly, messages won't flow correctly between services.</p>\n<h2>Dapr and .NET Aspire: Better Together</h2>\n<p><a href=\"https://www.diagrid.io/blog/net-aspire-dapr-what-are-they-and-how-they-complement-each-other\">Dapr works seamlessly with .NET Aspire</a>,\nMicrosoft's new cloud-ready stack for building distributed applications.\nWhile Aspire focuses on .NET-specific application orchestration, Dapr provides language-agnostic building blocks.</p>\n<p>Here's how to integrate Dapr with a .NET Aspire application:</p>\n<pre><code class=\"language-csharp\">using CommunityToolkit.Aspire.Hosting.Dapr;\n\n// Program.cs in the Aspire AppHost project\nvar builder = DistributedApplication.CreateBuilder(args);\n\n// Add Aspire service and configure Dapr\nvar orderService = builder.AddProject&lt;Projects.OrderService&gt;(&quot;orderservice&quot;)\n    .WithDaprSidecar(new DaprSidecarOptions\n    {\n        AppId = &quot;order-api&quot;,\n        Config = &quot;./dapr/config.yaml&quot;,\n        ResourcesPaths = [&quot;./dapr/components&quot;]\n    });\n\n// Add another service that can communicate with the order service via Dapr\nvar checkoutService = builder.AddProject&lt;Projects.CheckoutService&gt;(&quot;checkoutservice&quot;)\n    .WithDaprSidecar(new DaprSidecarOptions\n    {\n        AppId = &quot;checkout-api&quot;,\n        Config = &quot;./dapr/config.yaml&quot;,\n        ResourcesPaths = [&quot;./dapr/components&quot;]\n    })\n    // Reference the order service by its Dapr app ID\n    .WithReference(orderService);\n\nbuilder.Build().Run();\n</code></pre>\n<p>Note that I'm using the <code>CommunityToolkit.Aspire.Hosting.Dapr</code> package, which is the official Dapr integration for .NET Aspire.\nThe <code>Aspire.Hosting.Dapr</code> library is now deprecated.</p>\n<p>Here's an example of how a message flow might look in the Aspire dashboard:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_aspire_distributed_trace.png\" alt=\"A distributed trace from the Aspire dashboard showing a message flow from the checkout service to the order service.\">\n<h2>Learning with Dapr University</h2>\n<p>If you're looking for a structured way to learn Dapr, I highly recommend checking out <a href=\"https://diagrid.ws/41oIYRX\"><strong>Dapr University</strong></a>.\nYou can run the hands-on lessons completely for free.</p>\n<p>As someone who started with limited Dapr experience, I found the &quot;Dapr 101&quot; course particularly valuable.\nIt provides hands-on exercises for State Management, Service Invocation, and Pub/Sub—exactly what you need to get started quickly.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_131/dapr_university.png\" alt=\"Dapr university learning platform.\">\n<h2>Conclusion</h2>\n<p>Dapr simplifies microservice development for .NET developers by providing standardized building blocks that handle infrastructure complexity.\nWith its sidecar pattern, Dapr lets you focus on business logic while it manages cross-cutting concerns.\nAs you build distributed applications, consider how Dapr can help you:</p>\n<ul>\n<li>Accelerate development with ready-made patterns</li>\n<li>Build more resilient systems with fewer lines of code</li>\n<li>Avoid vendor lock-in through abstraction (building blocks)</li>\n<li>Improve production reliability with built-in best practices</li>\n</ul>\n<p>Ready to dive deeper?\nCheck out <a href=\"https://diagrid.ws/41oIYRX\"><strong>Dapr University</strong></a> for comprehensive courses and hands-on learning.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/introduction-to-dapr-for-dotnet-developers",
            "title": "Introduction to Dapr for .NET Developers",
            "summary": "Explore how Dapr helps .NET developers build better microservices with standardized building blocks, practical code examples, and seamless integration with…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_131.png",
            "date_modified": "2025-03-01T00:00:00.000Z",
            "date_published": "2025-03-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-a-better-mediatr-publisher-with-channels-and-why-you-shouldnt",
            "content_html": "<p>MediatR notifications are not asynchronous in the background-processing sense.\nBoth built-in publishers, <code>ForeachAwaitPublisher</code> and <code>TaskWhenAllPublisher</code>, block the publishing thread until every handler finishes.\nA custom <code>INotificationPublisher</code> backed by a bounded channel returns immediately, but queued notifications are lost if the process crashes.</p>\n<p>I've been meaning to write this article for a while now.\nThis problem has been bugging me, and I finally found the time to address it.</p>\n<p>What problem is that?</p>\n<p>Well, it's about MediatR's <a href=\"https://milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel\">notification publishing</a> mechanism.</p>\n<p>MediatR supports simple in-process publish/subscribe capabilities.\nThis lets you broadcast notifications to multiple handlers without coupling them directly to the publisher.</p>\n<p>While MediatR's notification system appears asynchronous at first glance, <strong>it's not</strong>.</p>\n<p>By asynchronous, I mean that the publishing thread should not wait for all handlers to complete.\nInstead, it should return immediately after queuing the notification for processing.</p>\n<p>In this article, we'll understand MediatR's notification publishing mechanics.\nWe'll use <a href=\"https://milanjovanovic.tech/blog/opentelemetry-dotnet-guide\"><strong>distributed tracing</strong></a> to examine its execution model, and explore alternatives for true asynchronous processing.</p>\n<h2>The Notification Publisher</h2>\n<p><a href=\"https://github.com/jbogard/MediatR\">MediatR</a> provides two built-in implementations of its <code>INotificationPublisher</code> interface.\nThey each have distinct characteristics but share one crucial trait: they block the publishing thread until the handlers complete.</p>\n<p>Here's the <code>INotificationPublisher</code> interface:</p>\n<pre><code class=\"language-csharp\">public interface INotificationPublisher\n{\n    Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken);\n}\n</code></pre>\n<p>This interface provides the contract for executing notification handlers, but the execution strategy is left to the implementing classes.</p>\n<p>By default, MediatR uses the <code>ForeachAwaitPublisher</code>:</p>\n<pre><code class=\"language-csharp\">public class ForeachAwaitPublisher : INotificationPublisher\n{\n    public async Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken)\n    {\n        foreach (var handler in handlerExecutors)\n        {\n            await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false);\n        }\n    }\n}\n</code></pre>\n<p>This implementation processes handlers sequentially, ensuring a predictable order of execution.</p>\n<p>The alternative <code>TaskWhenAllPublisher</code> offers concurrent execution:</p>\n<pre><code class=\"language-csharp\">public class TaskWhenAllPublisher : INotificationPublisher\n{\n    public Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken)\n    {\n        var tasks = handlerExecutors\n            .Select(handler =&gt; handler.HandlerCallback(notification, cancellationToken))\n            .ToArray();\n\n        return Task.WhenAll(tasks);\n    }\n}\n</code></pre>\n<p>While this publisher executes handlers concurrently, it's crucial to understand that &quot;concurrent&quot; doesn't mean <a href=\"https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way\">&quot;background processing&quot;</a>.\nThe publishing thread still waits for all handlers to complete before continuing.</p>\n<h2>Proving the Point with OpenTelemetry</h2>\n<p>To demonstrate the blocking nature of both publishers, let's set up a simple example with <a href=\"https://opentelemetry.io/\">OpenTelemetry</a> tracing:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\n// This will register all handlers in the same assembly as the Program class\n// We're also configuring the notifcation publisher\nbuilder.Services.AddMediatR(cfg =&gt;\n{\n    cfg.RegisterServicesFromAssemblyContaining&lt;Program&gt;();\n\n    cfg.NotificationPublisherType = typeof(ForeachAwaitPublisher);\n    // or we could say 👇\n    // cfg.NotificationPublisherType = typeof(TaskWhenAllPublisher);\n});\n\nbuilder.Services\n    .AddOpenTelemetry()\n    .ConfigureResource(r =&gt; r.AddService(DiagnosticConfig.Source.Name))\n    .WithTracing(tracing =&gt;\n        tracing\n            .AddAspNetCoreInstrumentation()\n            .AddSource(DiagnosticConfig.Source.Name))\n    .UseOtlpExporter();\n\nvar app = builder.Build();\n\n// Dummy endpoint to trigger the notification\napp.MapPost(&quot;orders&quot;, async (IMediator mediator) =&gt;\n{\n    using var activity = DiagnosticConfig.Source.StartActivity(&quot;CreateOrder&quot;);\n\n    var orderId = Guid.NewGuid();\n    // Just publish the notification, we don't care about doing &quot;real&quot; work here\n    await mediator.Publish(new OrderCreatedNotification\n    {\n        OrderId = orderId,\n        ParentId = activity?.Id // Propagating the parent activity ID\n    });\n\n    return Results.Ok(orderId);\n});\n\napp.Run();\n\n// The simple notification class\npublic class OrderCreatedNotification : INotification\n{\n    public Guid OrderId { get; set; }\n    public string? ParentId { get; set; }\n}\n\n// A slow handler to simulate blocking behavior\npublic class SlowOrderCreatedHandler(ILogger&lt;SlowOrderCreatedHandler&gt; logger)\n    : INotificationHandler&lt;OrderCreatedNotification&gt;\n{\n    public async Task Handle(OrderCreatedNotification notification, CancellationToken token)\n    {\n        using var activity = DiagnosticConfig.Source.StartActivity(\n            &quot;SlowOrderCreatedHandler.Handle&quot;,\n            ActivityKind.Internal,\n            notification.ParentId);\n\n        await Task.Delay(2000, token); // Simulate work\n\n        logger.LogInformation(\n            &quot;Slow handler completed for order {OrderId}&quot;,\n            notification.OrderId);\n    }\n}\n\n// Defines the OpenTelemetry ActivitySource\ninternal static class DiagnosticConfig\n{\n    internal static readonly ActivitySource Source = new(&quot;Order.Service&quot;);\n}\n</code></pre>\n<p>When we examine the resulting traces, we'll see that the handler execution spans are contained within the HTTP request span,\nindicating that the request thread is blocked until all handlers complete.</p>\n<p>Now, let's see how these publishers behave in practice.\nI'll add a few more handlers to the mix to make the example more interesting.</p>\n<h3>ForeachAwaitPublisher Traces</h3>\n<p>You can see the sequential execution of handlers in the trace visualization.\nThe request span encompasses all handler execution, demonstrating the blocking nature of the <code>ForeachAwaitPublisher</code>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_130/foreachawait_publisher.png\" alt=\"Distributed trace demonstrating notification handling.\">\n<h3>TaskWhenAllPublisher Traces</h3>\n<p>Similarly, the <code>TaskWhenAllPublisher</code> shows concurrent handler execution within the request span.\nWe do get a slight improvement in handler execution time, but the request thread still waits for all handlers to complete before returning.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_130/taskwhenall_publisher.png\" alt=\"Distributed trace demonstrating notification handling.\">\n<h2>Building an Async Notification Publisher with Channels</h2>\n<p>How can we make MediatR's notification publishing truly asynchronous?</p>\n<p>We'll implement a custom <code>INotificationPublisher</code> that leverages <code>System.Threading.Channels</code> for true asynchronous processing.\nThis implementation will queue notifications for background processing, allowing the publishing thread to return immediately.</p>\n<p>Here's the <code>ChannelPublisher</code>:</p>\n<pre><code class=\"language-csharp\">// The publisher just queues the notification for processing\npublic class ChannelPublisher(NotificationsQueue queue) : INotificationPublisher\n{\n    public async Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken)\n    {\n        // Write the message to the channel, and return immediately\n        await queue.Writer.WriteAsync(\n            new NotificationEntry(handlerExecutors.ToArray(), notification),\n            cancellationToken);\n    }\n}\n\n// It's the Channel that handles the actual message passing\n// We can control the capacity and backpressure handling here\npublic class NotificationsQueue(int capacity = 100)\n{\n    private readonly Channel&lt;NotificationEntry&gt; _queue =\n        Channel.CreateBounded&lt;NotificationEntry&gt;(new BoundedChannelOptions(capacity)\n        {\n            FullMode = BoundedChannelFullMode.Wait // Backpressure handling\n        });\n\n    public ChannelReader&lt;NotificationEntry&gt; Reader =&gt; _queue.Reader;\n    public ChannelWriter&lt;NotificationEntry&gt; Writer =&gt; _queue.Writer;\n}\n\n// A simple data structure to hold the notification and handlers\npublic record NotificationEntry(NotificationHandlerExecutor[] Handlers, INotification Notification);\n\n// Program.cs\nbuilder.Services.AddSingleton&lt;NotificationsQueue&gt;();\n</code></pre>\n<p>But this is just part of the solution.\nWe need a background service to process the queued notifications:</p>\n<pre><code class=\"language-csharp\">// We'll use the NotificationsQueue to read and process notifications\npublic class ChannelPublisherWorker(NotificationsQueue queue) : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        // Read notifications from the queue and process them\n        await foreach (NotificationEntry entry in queue.Reader.ReadAllAsync(stoppingToken))\n        {\n            // Parallel.ForEachAsync for style points\n            await Parallel.ForEachAsync(entry.Handlers, stoppingToken, async (executor, token) =&gt;\n            {\n                // We're finally executing the handler\n                await executor.HandlerCallback(entry.Notification, token);\n            });\n        }\n    }\n}\n\n// Program.cs\nbuilser.Services.AddHostedService&lt;ChannelPublisherWorker&gt;();\n</code></pre>\n<p>This implementation offers several advantages:</p>\n<ul>\n<li>True background processing - the publisher returns immediately after queueing the notification</li>\n<li>Backpressure handling through bounded channel capacity</li>\n<li>Independent handler execution</li>\n</ul>\n<p>To use this publisher, register it with MediatR by setting the <code>NotificationPublisherType</code> to be <code>ChannelPublisher</code>:</p>\n<pre><code class=\"language-csharp\">services.AddMediatR(cfg =&gt;\n{\n    cfg.NotificationPublisherType = typeof(ChannelPublisher);\n});\n</code></pre>\n<p>Let's see how this implementation performs in practice.</p>\n<h2>Comparing Approaches With OpenTelemetry</h2>\n<p>When we examine the traces with our <code>ChannelPublisher</code>, we'll see a significant difference:</p>\n<ol>\n<li>The HTTP request span completes quickly after queueing the notification</li>\n<li>Handler execution spans appear as separate traces</li>\n<li>Overall system responsiveness improves</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_130/channel_publisher.png\" alt=\"Distributed trace demonstrating notification handling.\">\n<p>This visualization clearly demonstrates the non-blocking nature of our implementation.</p>\n<p>But is it worth it?</p>\n<p>Here's what you should consider first before adopting this approach:</p>\n<ul>\n<li>The <code>ChannelPublisher</code> introduces additional complexity compared to the built-in publishers</li>\n<li>Error handling is your responsibility (e.g., retrying failed handlers, <a href=\"https://en.wikipedia.org/wiki/Dead_letter_queue\">dead-letter queue</a>)</li>\n<li>And did I mention <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\">idempotent consumers</a>? Yeah... you need those too</li>\n<li><a href=\"https://milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels\">Channels</a> aren't durable - messages are lost if the application crashes</li>\n</ul>\n<p>Before you know it, you might find yourself reinventing the wheel with a custom message queueing system.</p>\n<p>Instead, consider using a real message broker like <a href=\"https://www.rabbitmq.com/\">RabbitMQ</a>.\nCombine it with a library like <a href=\"https://masstransit.io\">MassTransit</a> or <a href=\"https://particular.net/nservicebus\">NServiceBus</a> for a robust, scalable, and reliable messaging solution.</p>\n<h2>Takeaway</h2>\n<p>MediatR's notification system is great for simple in-process pub/sub scenarios.\nHowever, the built-in publishers can become a bottleneck in high-throughput applications due to their blocking nature.</p>\n<p>The <code>ChannelPublisher</code> implementation we explored offers true asynchronous processing.\nHowever, it also comes with extra complexity around message handling and delivery guarantees.\nManaging message persistence, error handling, retries, and idempotency quickly becomes challenging.</p>\n<p>If your application requires these features,\nyou'll be better off adopting a mature solution like <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\">RabbitMQ</a>\nor <a href=\"https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit\">Amazon SQS</a>.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-a-better-mediatr-publisher-with-channels-and-why-you-shouldnt",
            "title": "Building a Better MediatR Publisher With Channels (and why you shouldn't)",
            "summary": "Discover why MediatR's notification publishers block your application, and explore a Channel-based solution before reaching for a message queue.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_130.png",
            "date_modified": "2025-02-22T00:00:00.000Z",
            "date_published": "2025-02-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive",
            "content_html": "<p>Cursor pagination filters on the last row you saw instead of using <code>OFFSET</code>, which forces the database to scan and discard every row before the page you asked for.\nPerformance stays flat no matter how deep you page.\nIn my PostgreSQL tests on a million rows, the same page dropped from 704 ms to 41 ms.</p>\n<p>Pagination is crucial for efficiently handling large datasets.\nWhile offset pagination is widely used and gets the job done, cursor-based pagination offers some interesting advantages for certain scenarios.</p>\n<p>It's particularly valuable for real-time feeds, infinite scroll interfaces, and APIs where performance at scale matters -\nlike social media timelines, activity logs, or event streams where users frequently page through large datasets.</p>\n<p>Let's explore both approaches using a simple <code>UserNotes</code> table and see how they perform with a million records.</p>\n<p>We'll look at the implementation details, compare query performance, and discuss where each approach makes the most sense.</p>\n<p>I've included real execution plans from <a href=\"https://www.postgresql.org\">PostgreSQL</a> to demonstrate the significant performance differences between these approaches.</p>\n<h2>Database Schema</h2>\n<p>I created a simple table to demonstrate pagination techniques.\nThe table is seeded with <code>1,000,000</code> records for testing purposes, which should be enough to show the performance difference between offset and cursor pagination.</p>\n<p>We'll use the following SQL schema for the examples:</p>\n<pre><code class=\"language-sql\">CREATE TABLE user_notes (\n    id uuid NOT NULL,\n    user_id uuid NOT NULL,\n    note character varying(500),\n    date date NOT NULL,\n    CONSTRAINT pk_user_notes PRIMARY KEY (id)\n);\n</code></pre>\n<p>And here's the C# class representing the <code>UserNote</code> entity:</p>\n<pre><code class=\"language-csharp\">public class UserNote\n{\n    public Guid Id { get; set; }\n    public Guid UserId { get; set; }\n    public string? Note { get; set; }\n    public DateOnly Date { get; set; }\n}\n</code></pre>\n<p>I will use PostgreSQL as the database, but the concepts also apply to other databases.</p>\n<h2>Offset Pagination: The Traditional Approach</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/querying/pagination#offset-pagination\">Offset pagination</a> uses <code>Skip</code> and <code>Take</code> operations.\nWe <em>skip</em> a certain number of rows and <em>take</em> a fixed number of rows.\nThese usually translate to <code>OFFSET</code> and <code>LIMIT</code> in SQL queries.</p>\n<p>Here's an example of offset pagination in ASP.NET Core:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/offset&quot;, async (\n    AppDbContext dbContext,\n    int page = 1,\n    int pageSize = 10,\n    CancellationToken cancellationToken = default) =&gt;\n{\n    if (page &lt; 1) return Results.BadRequest(&quot;Page must be greater than 0&quot;);\n    if (pageSize &lt; 1) return Results.BadRequest(&quot;Page size must be greater than 0&quot;);\n    if (pageSize &gt; 100) return Results.BadRequest(&quot;Page size must be less than or equal to 100&quot;);\n\n    var query = dbContext.UserNotes\n        .OrderByDescending(x =&gt; x.Date)\n        .ThenByDescending(x =&gt; x.Id);\n\n    // Offset pagination typically counts the total number of items\n    var totalCount = await query.CountAsync(cancellationToken);\n    var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);\n\n    // Skip and take the required number of items\n    var items = await query\n        .Skip((page - 1) * pageSize)\n        .Take(pageSize)\n        .ToListAsync(cancellationToken);\n\n    return Results.Ok(new\n    {\n        Items = items,\n        Page = page,\n        PageSize = pageSize,\n        TotalCount = totalCount,\n        TotalPages = totalPages,\n        HasNextPage = page &lt; totalPages,\n        HasPreviousPage = page &gt; 1\n    });\n});\n</code></pre>\n<p>Note that I'm sorting the results by <code>Date</code> and <code>Id</code> in descending order.\nThis ensures consistent results when paginating.</p>\n<p>Here's the generated SQL for offset pagination:</p>\n<pre><code class=\"language-sql\">-- This query is sent first\nSELECT count(*)::int FROM user_notes AS u;\n\n-- Followed by the actual data query\nSELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nORDER BY u.date DESC, u.id DESC\nLIMIT @pageSize OFFSET @offset;\n</code></pre>\n<h3>Limitations of Offset Pagination:</h3>\n<ol>\n<li>Performance degrades as offset increases because the database must scan and discard all rows before the offset</li>\n<li>Risk of missing or duplicating items when data changes between pages</li>\n<li>Inconsistent results with concurrent updates</li>\n</ol>\n<h2>Cursor-Based Pagination: A Faster Approach</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/querying/pagination#keyset-pagination\">Cursor pagination</a> uses a reference point (cursor) to fetch the next set of results.\nThis reference point is typically a <strong>unique identifier</strong> or a combination of fields that define the sort order.</p>\n<p>I'll use the <code>Date</code> and <code>Id</code> fields to create a cursor for our <code>UserNotes</code> table.\nThe cursor is a composite of these two fields, allowing us to paginate efficiently.</p>\n<p>Here's an example of cursor pagination in ASP.NET Core:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/cursor&quot;, async (\n    AppDbContext dbContext,\n    DateOnly? date = null,\n    Guid? lastId = null,\n    int limit = 10,\n    CancellationToken cancellationToken = default) =&gt;\n{\n    if (limit &lt; 1) return Results.BadRequest(&quot;Limit must be greater than 0&quot;);\n    if (limit &gt; 100) return Results.BadRequest(&quot;Limit must be less than or equal to 100&quot;);\n\n    var query = dbContext.UserNotes.AsQueryable();\n\n    if (date != null &amp;&amp; lastId != null)\n    {\n        // Use the cursor to fetch the next set of results\n        // If we were sorting in ASC order, we'd use &gt; instead of &lt;\n        query = query.Where(x =&gt; x.Date &lt; date || (x.Date == date &amp;&amp; x.Id &lt;= lastId));\n    }\n\n    // Fetch the items and determine if there are more\n    var items = await query\n        .OrderByDescending(x =&gt; x.Date)\n        .ThenByDescending(x =&gt; x.Id)\n        .Take(limit + 1)\n        .ToListAsync(cancellationToken);\n\n    // Extract the cursor and ID for the next page\n    bool hasMore = items.Count &gt; limit;\n    DateOnly? nextDate = hasMore ? items[^1].Date : null;\n    Guid? nextLastId = hasMore ? items[^1].Id : null;\n\n    // Remove the extra item before returning results\n    if (hasMore)\n    {\n        items.RemoveAt(items.Count - 1);\n    }\n\n    return Results.Ok(new\n    {\n        Items = items,\n        NextDate = nextDate,\n        NextLastId = nextLastId,\n        HasMore = hasMore\n    });\n});\n</code></pre>\n<p>The sort order is the same as in the offset pagination example.\nHowever, the sort order is critical for consistent results with cursor pagination.\nBecause the <code>Date</code> isn't a unique value in our table, we use the <code>Id</code> field to handle ties.\nThis ensures that we don't miss or duplicate items when paginating.</p>\n<p>Here's the generated SQL for cursor pagination:</p>\n<pre><code class=\"language-sql\">SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nWHERE u.date &lt; @date OR (u.date = @date AND u.id &lt;= @lastId)\nORDER BY u.date DESC, u.id DESC\nLIMIT @limit;\n</code></pre>\n<p>Note that there's no <code>OFFSET</code> in the query.\nWe're directly seeking the rows based on the cursor, which is more efficient than offset pagination.</p>\n<p>The <code>COUNT</code> query is omitted in cursor pagination because we're not counting the total number of items.\nThis can be a limitation if you need to display the total number of pages upfront.\nHowever, the performance benefits of cursor pagination often outweigh this limitation.</p>\n<h3>Limitations of Cursor Pagination:</h3>\n<ol>\n<li>If users need to change sort fields dynamically, cursor pagination becomes significantly more complicated since the cursor must incorporate all sort conditions</li>\n<li>Users can't jump to a specific page number - they must traverse sequentially through the pages</li>\n<li>More complex to implement correctly compared to offset pagination, especially when handling ties and ensuring stable ordering</li>\n</ol>\n<h2>Examining the SQL Execution Plans</h2>\n<p>I wanted to compare the execution plans for offset and cursor pagination.\nI used the <code>EXPLAIN ANALYZE</code> command in PostgreSQL to see the <a href=\"https://www.postgresql.org/docs/current/using-explain.html\">query plans</a>.</p>\n<p>Here's the offset pagination query:</p>\n<pre><code class=\"language-sql\">SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000 OFFSET 900000;\n</code></pre>\n<p>I'm intentionally skipping <code>900,000</code> rows to exaggerate the performance impact.\nAfter that, we fetch the next <code>1,000</code> rows.</p>\n<p>Here's the query plan for offset pagination:</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000 OFFSET 900000;\n\n---\nLimit  (cost=165541.59..165541.71 rows=1 width=52) (actual time=695.026..701.406 rows=1000 loops=1)\n  -&gt;  Gather Merge  (cost=68312.50..165541.59 rows=833334 width=52) (actual time=342.475..684.567 rows=901000 loops=1)\n        Workers Planned: 2\n        Workers Launched: 2\n        -&gt;  Sort  (cost=67312.48..68354.15 rows=416667 width=52) (actual time=327.846..450.295 rows=300841 loops=3)\n              Sort Key: date DESC, id DESC\n              Sort Method: external merge  Disk: 20440kB\n              Worker 0:  Sort Method: external merge  Disk: 18832kB\n              Worker 1:  Sort Method: external merge  Disk: 18912kB\n              -&gt;  Parallel Seq Scan on user_notes u  (cost=0.00..14174.67 rows=416667 width=52) (actual time=1.035..22.876 rows=333333 loops=3)\nPlanning Time: 0.050 ms\nJIT:\n  Functions: 8\n  Options: Inlining false, Optimization false, Expressions true, Deforming true\n  Timing: Generation 0.243 ms (Deform 0.111 ms), Inlining 0.000 ms, Optimization 0.270 ms, Emission 4.085 ms, Total 4.598 ms\nExecution Time: 704.217 ms\n</code></pre>\n<p>The total execution time is <code>704.217 ms</code> for offset pagination.</p>\n<p>Here's the query returning the same set of rows using cursor pagination.\nI had to hardcode the <code>@date</code> and <code>@lastId</code> values for this comparison:</p>\n<pre><code class=\"language-sql\">SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nWHERE u.date &lt; @date OR (u.date = @date AND u.id &lt;= @lastId)\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000;\n</code></pre>\n<p>Finally, here's the query plan for cursor pagination:</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nWHERE u.date &lt; @date OR (u.date = @date AND u.id &lt;= @lastId)\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000;\n\n---\nLimit  (cost=20605.63..20722.31 rows=1000 width=52) (actual time=37.993..40.958 rows=1000 loops=1)\n  -&gt;  Gather Merge  (cost=20605.63..30419.62 rows=84114 width=52) (actual time=37.992..40.921 rows=1000 loops=1)\n        Workers Planned: 2\n        Workers Launched: 2\n        -&gt;  Sort  (cost=19605.61..19710.75 rows=42057 width=52) (actual time=24.611..24.630 rows=811 loops=3)\n              Sort Key: date DESC, id DESC\n              Sort Method: top-N heapsort  Memory: 240kB\n              Worker 0:  Sort Method: top-N heapsort  Memory: 239kB\n              Worker 1:  Sort Method: top-N heapsort  Memory: 238kB\n              -&gt;  Parallel Seq Scan on user_notes u  (cost=0.00..17299.67 rows=42057 width=52) (actual time=0.009..21.462 rows=33333 loops=3)\n                    Filter: ((date &lt; @date::date) OR ((date = @date::date) AND (id &lt;= @lastId::uuid)))\n                    Rows Removed by Filter: 300000\nPlanning Time: 0.063 ms\nExecution Time: 40.993 ms\n</code></pre>\n<p>The total execution time for cursor pagination is <code>40.993 ms</code>.</p>\n<p>A whopping <code>17x</code> performance improvement with cursor pagination compared to offset pagination!</p>\n<p>The performance with cursor pagination is consistent regardless of the page depth.\nThis is because we're directly seeking the rows based on the cursor, which is more efficient than offset pagination.\nIt's a huge advantage over offset pagination, especially for large datasets.</p>\n<h2>Adding Indexes for Cursor Pagination</h2>\n<p>I also tested the impact of indexes on <a href=\"https://use-the-index-luke.com/blog/2013-07/pagination-done-the-postgresql-way\">cursor pagination</a>.\nI created a composite index on the <code>Date</code> and <code>Id</code> fields to speed up the queries.\nOr so I thought...</p>\n<p>Here's the SQL command to create the composite index:</p>\n<pre><code class=\"language-sql\">CREATE INDEX idx_user_notes_date_id ON user_notes (date DESC, id DESC);\n</code></pre>\n<p>The index is created in descending order to match the sort order in our queries.</p>\n<p>Let's see the query plan for cursor pagination with the composite index:</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nWHERE u.date &lt; @date OR (u.date = @date AND u.id &lt;= @lastId)\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000;\n\n---\nLimit  (cost=0.42..816.55 rows=1000 width=52) (actual time=298.534..298.924 rows=1000 loops=1)\n  -&gt;  Index Scan using idx_user_notes_date_id on user_notes u  (cost=0.42..82376.42 rows=100936 width=52) (actual time=298.532..298.888 rows=1000 loops=1)\n        Filter: ((date &lt; @date::date) OR ((date = @date::date) AND (id &lt;= @lastId::uuid)))\n        Rows Removed by Filter: 900000\nPlanning Time: 0.068 ms\nExecution Time: 298.955 ms\n</code></pre>\n<p>We have an <code>Index Scan</code> using the composite index.\nHowever, the execution time is <code>298.955 ms</code>, which is slower than the previous query without the index.</p>\n<p>This might be because the dataset is too small to benefit from the index.\nI have only <code>1,000,000</code> records in the table, which might not be enough to see the performance improvement with the index.</p>\n<p>But wait, there's more to it!</p>\n<p>What if we were to use a tuple comparison in SQL?</p>\n<pre><code class=\"language-sql\">EXPLAIN ANALYZE SELECT u.id, u.date, u.note, u.user_id\nFROM user_notes AS u\nWHERE (u.date, u.id) &lt;= (@date, @lastId)\nORDER BY u.date DESC, u.id DESC\nLIMIT 1000;\n\n---\nLimit  (cost=0.42..432.81 rows=1000 width=52) (actual time=0.020..0.641 rows=1000 loops=1)\n  -&gt;  Index Scan using idx_user_notes_date_id on user_notes u  (cost=0.42..43817.85 rows=101339 width=52) (actual time=0.019..0.606 rows=1000 loops=1)\n        Index Cond: (ROW(date, id) &lt;= ROW(@date::date, @lastId::uuid))\nPlanning Time: 0.060 ms\nExecution Time: 0.668 ms\n</code></pre>\n<p>Finally, the index is working.\nThe execution time is <code>0.668 ms</code>, which is significantly faster than the previous queries.</p>\n<p>The query optimizer cannot determine whether the <a href=\"https://milanjovanovic.tech/blog/sql-index-not-used-sargability\"><strong>composite index</strong></a> can be used for row-level comparison.\nHowever, the index is effectively used with a tuple comparison.</p>\n<p>How do you translate this to EF Core?</p>\n<p>The Postgres provider has <code>EF.Functions.LessThanOrEqual</code>, which accepts a <code>ValueTuple</code> as an argument.\nWe can use it to produce a <code>(u.date, u.id) &lt;= (@date, @lastId)</code> comparison in the query.\nAnd this will utilize the composite index.</p>\n<pre><code class=\"language-csharp\">query = query.Where(x =&gt; EF.Functions.LessThanOrEqual(\n    ValueTuple.Create(x.Date, x.Id),\n    ValueTuple.Create(date, lastId)));\n</code></pre>\n<h2>Encoding the Cursor</h2>\n<p>Here's a small utility class for encoding and decoding the cursor.\nWe'll use this to encode the cursor in the URL and decode it when fetching the next set of results.</p>\n<p>The clients will receive the cursor as a Base64-encoded string.\nThey don't need to know the internal structure of the cursor.</p>\n<pre><code class=\"language-csharp\">using Microsoft.AspNetCore.Authentication; // For Base64UrlTextEncoder\n\npublic sealed record Cursor(DateOnly Date, Guid LastId)\n{\n    public static string Encode(DateOnly date, string lastId)\n    {\n        var cursor = new Cursor(date, lastId);\n        string json = JsonSerializer.Serialize(cursor);\n        return Base64UrlTextEncoder.Encode(Encoding.UTF8.GetBytes(json));\n    }\n\n    public static Cursor? Decode(string? cursor)\n    {\n        if (string.IsNullOrWhiteSpace(cursor))\n        {\n            return null;\n        }\n\n        try\n        {\n            string json = Encoding.UTF8.GetString(Base64UrlTextEncoder.Decode(cursor));\n            return JsonSerializer.Deserialize&lt;Cursor&gt;(json);\n        }\n        catch\n        {\n            return null;\n        }\n    }\n}\n</code></pre>\n<p>Here's an example of encoding and decoding the cursor:</p>\n<pre><code class=\"language-csharp\">string encodedCursor = Cursor.Encode(\n  new DateOnly(2025, 2, 15),\n  Guid.Parse(&quot;019500f9-8b41-74cf-ab12-25a48d4d4ab4&quot;));\n// Result:\n// eyJEYXRlIjoiMjAyNS0wMi0xNSIsIkxhc3RJZCI6IjAxOTUwMGY5LThiNDEtNzRjZi1hYjEyLTI1YTQ4ZDRkNGFiNCJ9\n\nCursor decodedCursor = Cursor.Decode(encodedCursor);\n// Result:\n// {\n//     &quot;Date&quot;: &quot;2025-02-15&quot;,\n//     &quot;LastId&quot;: &quot;019500f9-8b41-74cf-ab12-25a48d4d4ab4&quot;\n// }\n</code></pre>\n<h2>Summary</h2>\n<p>While offset pagination is simpler to implement, it suffers from significant performance degradation at scale.\nMy tests showed a 17x slowdown compared to cursor pagination when accessing deeper pages.</p>\n<p>Cursor pagination maintains consistent performance regardless of page depth and works particularly well for real-time feeds and infinite scroll interfaces.</p>\n<p>However, cursor pagination comes with tradeoffs.\nIt requires careful implementation, especially around cursor encoding and handling sort orders.\nIt also doesn't provide total page counts, making it unsuitable for interfaces that need to support paged navigation.</p>\n<p>The choice between these approaches ultimately depends on your use case:</p>\n<ul>\n<li>Choose cursor pagination for performance-critical APIs, real-time feeds, infinite scroll, or any scenario where users frequently access deep pages</li>\n<li>Stick with offset pagination for admin interfaces, small datasets, or when you need upfront page counts</li>\n</ul>\n<p>Another thing to consider: which page will your users typically land on?\nIf most users start at the first page and rarely visit other pages, offset pagination might be sufficient.\nThis will be the case for many applications.</p>\n<p>Remember to use tuple comparisons and appropriate indexes to get the best performance from cursor pagination.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive",
            "title": "Understanding Cursor Pagination and Why It's So Fast (Deep Dive)",
            "summary": "Cursor-based pagination beats offset pagination as the pages get deeper. My tests show a 17x speedup on a million-record PostgreSQL dataset.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_129.png",
            "date_modified": "2025-02-15T00:00:00.000Z",
            "date_published": "2025-02-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/stop-conflating-cqrs-and-mediatr",
            "content_html": "<p>CQRS is an architectural pattern that separates read and write models, and it prescribes no specific library.\nMediatR is a different tool: it implements the mediator pattern for in-process messaging and pipeline behaviors.\nYou can implement CQRS with your own <code>ICommandHandler</code> and <code>IQueryHandler</code> interfaces and never install MediatR.</p>\n<p>&quot;We need to implement CQRS? Great, let me install MediatR.&quot;</p>\n<p>If you've heard this in your development team - or perhaps said it yourself - you're not alone.\nThe .NET ecosystem has gradually fused these two concepts together, creating an almost reflexive response: CQRS equals MediatR.</p>\n<p>This mental shortcut has led countless teams down a path of unnecessary complexity.\nOthers have avoided CQRS entirely, fearing the overhead of yet another messaging framework.</p>\n<p>In this article, we'll dispel some common misconceptions and highlight the benefits of each pattern.</p>\n<h2>Understanding CQRS in Its Pure Form</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs\">CQRS</a> is a pattern that separates read and write operations in your application.\nThe pattern suggests that the models used for reading data should be different from those used for writing data.</p>\n<p>That's it.</p>\n<p>No specific implementation details, no prescribed libraries, just a simple architectural principle.</p>\n<p>The pattern emerged from the understanding that in many applications, especially those with complex domains,\nthe requirements for reading and writing data are fundamentally different.\nRead operations often need to combine data from multiple sources or present it in specific formats for UI consumption.\nWrite operations need to enforce business rules, maintain consistency, and manage domain state.</p>\n<p>This separation provides several benefits:</p>\n<ul>\n<li>Optimized read and write models for their specific purposes</li>\n<li>Simplified maintenance as read and write concerns evolve independently</li>\n<li>Enhanced scalability options for read and write operations</li>\n<li>Clearer boundary between domain logic and presentation needs</li>\n</ul>\n<h2>MediatR: A Different Tool for Different Problems</h2>\n<p><a href=\"https://github.com/jbogard/MediatR\">MediatR</a> is an implementation of the mediator pattern.\nIts primary purpose is to reduce direct dependencies between components by providing a central point of communication.\nInstead of knowing about each other, the mediator connects the components.</p>\n<p>The library provides several features:</p>\n<ul>\n<li>In-process messaging between components</li>\n<li>Behavior pipelines for <a href=\"https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture\">cross-cutting concerns</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel\">Notification handling</a> (publish/subscribe)</li>\n</ul>\n<p>The indirection MediatR introduces is its most criticized aspect.\nIt can make code harder to follow, especially for newcomers to the codebase.\nHowever, you can easily solve this problem by defining the requests in the same file as the handler.</p>\n<h2>Why They Often Appear Together</h2>\n<p>The frequent pairing of CQRS and MediatR isn't without reason.\nMediatR's <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\">request/response model</a> aligns well with CQRS's command/query separation.\nCommands and queries can be implemented as MediatR requests, with handlers containing the actual implementation logic.</p>\n<p>Here's an example command using MediatR:</p>\n<pre><code class=\"language-csharp\">public record CreateHabit(string Name, string? Description, int Priority) : IRequest&lt;HabitDto&gt;;\n\npublic sealed class CreateHabitHandler(ApplicationDbContext dbContext, IValidator&lt;CreateHabit&gt; validator)\n    : IRequestHandler&lt;CreateHabit, HabitDto&gt;\n{\n    public async Task&lt;HabitDto&gt; Handle(CreateHabit request, CancellationToken cancellationToken)\n    {\n        await validator.ValidateAndThrowAsync(request);\n\n        Habit habit = request.ToEntity();\n\n        dbContext.Habits.Add(habit);\n\n        await dbContext.SaveChangesAsync(cancellationToken);\n\n        return habit.ToDto();\n    }\n}\n</code></pre>\n<p><a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\">CQRS with MediatR</a> offers several advantages:</p>\n<ul>\n<li>Consistent handling of both commands and queries</li>\n<li>Pipeline behaviors for logging, validation, and error handling</li>\n<li>Clear separation of concerns through handler classes</li>\n<li>Simplified testing through handler isolation</li>\n</ul>\n<p>However, this convenience comes at the cost of additional abstraction and complexity.\nWe have to define the request/response classes and handlers, write code for sending the requests, and so on.\nThis can be overkill for simple applications.</p>\n<p>The question isn't whether this trade-off is universally good or bad but whether it's appropriate for your specific context.</p>\n<h2>CQRS Without MediatR</h2>\n<p>CQRS can be implemented just as easily without MediatR.\nHere's a simple example of what it might look like.</p>\n<p>You can define commands and queries as simple interfaces:</p>\n<pre><code class=\"language-csharp\">public interface ICommandHandler&lt;in TCommand, TResult&gt;\n{\n    Task&lt;TResult&gt; Handle(TCommand command, CancellationToken cancellationToken = default);\n}\n\n// Same thing for IQueryHandler\n</code></pre>\n<p>Then, you can implement your handlers and register them with dependency injection:</p>\n<pre><code class=\"language-csharp\">public record CreateOrderCommand(string CustomerId, List&lt;OrderItem&gt; Items)\n    : ICommand&lt;CreateOrderResult&gt;;\n\npublic class CreateOrderCommandHandler : ICommandHandler&lt;CreateOrderCommand, CreateOrderResult&gt;\n{\n    public async Task&lt;CreateOrderResult&gt; Handle(\n        CreateOrderCommand command,\n        CancellationToken cancellationToken = default)\n    {\n        // implementation\n    }\n}\n\n// DI registration...\nbuilder.Services\n    .AddScoped&lt;ICommandHandler&lt;CreateOrderCommand, CreateOrderResult&gt;, CreateOrderCommandHandler&gt;();\n</code></pre>\n<p>Finally, you can use the handler in your controller:</p>\n<pre><code class=\"language-csharp\">[ApiController]\n[Route(&quot;orders&quot;)]\npublic class OrdersController : ControllerBase\n{\n    [HttpPost]\n    public async Task&lt;ActionResult&lt;CreateOrderResult&gt;&gt; CreateOrder(\n        CreateOrderCommand command,\n        ICommandHandler&lt;CreateOrderCommand, CreateOrderResult&gt; handler)\n    {\n        var result = await handler.Handle(command);\n\n        return Ok(result);\n    }\n}\n</code></pre>\n<p>What's the difference between this and the MediatR approach?</p>\n<p>This approach provides the same separation of concerns but without the indirection.\nIt's direct, explicit, and often sufficient for many applications.</p>\n<p>However, it lacks some of the conveniences that MediatR offers, such as <a href=\"https://milanjovanovic.tech/blog/mediatr-pipeline-behaviors\"><strong>pipeline behaviors</strong></a> and automatically registering handlers.\nYou also need to inject the specific handlers into your controllers, which can be cumbersome for larger applications.</p>\n<h2>Takeaway</h2>\n<p>CQRS and MediatR are distinct tools that solve different problems.\nWhile they can work well together, treating them as inseparable does a disservice to both.\nCQRS separates read and write concerns, while MediatR decouples components through a mediator.</p>\n<p>The key is understanding what each pattern offers and making informed decisions based on your specific context.\nSometimes, you'll want both, sometimes just one, and sometimes neither.\nThat's the essence of thoughtful architecture: choosing the right tools for your specific needs.</p>\n<p>If you want to learn more about implementing CQRS effectively as part of a clean,\nmaintainable architecture, check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.\nYou'll learn how to apply these patterns in real-world scenarios, avoiding common pitfalls and over-engineering while building scalable applications.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/stop-conflating-cqrs-and-mediatr",
            "title": "Stop Conflating CQRS and MediatR",
            "summary": "The .NET ecosystem has fused CQRS and MediatR together, and that shortcut leads teams into unnecessary complexity. They solve different problems.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_128.png",
            "date_modified": "2025-02-08T00:00:00.000Z",
            "date_published": "2025-02-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/overriding-default-http-resilience-handlers-in-dotnet",
            "content_html": "<p><code>ConfigureHttpClientDefaults</code> with <code>AddStandardResilienceHandler</code> applies one Polly pipeline to every <code>HttpClient</code>, and it overrides any per-client pipeline you add afterwards.\nThe workaround is an extension method that strips every <code>ResilienceHandler</code> from the client before you add your own.\nThe .NET team merged a pull request adding proper support for a future release.</p>\n<p>Introducing <a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\"><strong>.NET 8 resilience packages</strong></a>\nbuilt on top of <a href=\"https://github.com/App-vNext/Polly\">Polly</a>\nhas made it much easier to build robust HTTP clients.\nThese packages provide standard resilience handlers that you can easily attach to <code>HttpClient</code> instances.\nThey implement common patterns like retry, circuit breaker, and timeout policies.</p>\n<p>However, there is a significant limitation: once you configure the standard resilience handlers globally for all clients,\nthere is no built-in way to override them for specific cases.\nThis can be problematic when different endpoints require different resilience strategies.</p>\n<p>In today's issue, I'll show you how to fix this and what the .NET team is doing about it.</p>\n<h2>Standard Resilience Configuration</h2>\n<p>Let's say you've configured default resilience handlers in your application startup.\n<code>ConfigureHttpClientDefaults</code> is a convenient way to add standard resilience handlers to all <code>HttpClient</code> instances:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddHttpClient()\n    .ConfigureHttpClientDefaults(http =&gt; http.AddStandardResilienceHandler());\n</code></pre>\n<p>The .NET team runs many large-scale services in production, and they've found a standard set of resilience strategies that work\nwell for most scenarios.</p>\n<p>The standard resilience handler combines five strategies to create a <a href=\"https://milanjovanovic.tech/blog/polly-v8-resilience-pipelines\"><strong>resilience pipeline</strong></a>:</p>\n<ul>\n<li>Rate limiter</li>\n<li>Total request timeout</li>\n<li>Retry</li>\n<li>Circuit breaker</li>\n<li>Attempt timeout</li>\n</ul>\n<p>You can customize the standard resilience pipeline by configuring the <code>HttpStandardResilienceOptions</code>.</p>\n<p>Here's an example of how to configure it:</p>\n<pre><code class=\"language-csharp\">builder.Services.ConfigureHttpClientDefaults(http =&gt; http.AddStandardResilienceHandler(options =&gt;\n{\n    // Default is 2 seconds.\n    options.Retry.Delay = TimeSpan.FromSeconds(1);\n\n    // Default is 30 seconds.\n    options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(20);\n\n    // Default is 0.1.\n    options.CircuitBreaker.FailureRatio = 0.2;\n}));\n</code></pre>\n<p>Okay, so we have our standard resilience pipeline set up.\nNow all your <code>HttpClient</code> instances will use these resilience policies.</p>\n<p>But what if you need different retry logic for a specific API endpoint or need to turn off circuit breaking for specific calls?</p>\n<h2>The Problem</h2>\n<p>Let's say you have a named <code>HttpClient</code> for calling the GitHub API,\nand you want to configure specific resilience strategies for it:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddHttpClient(&quot;github&quot;)\n    .ConfigureHttpClient(client =&gt;\n    {\n        client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n    })\n    .AddResilienceHandler(&quot;custom&quot;, pipeline =&gt;\n    {\n        pipeline.AddTimeout(TimeSpan.FromSeconds(10));\n\n        pipeline.AddRetry(new HttpRetryStrategyOptions\n        {\n            MaxRetryAttempts = 3,\n            BackoffType = DelayBackoffType.Exponential,\n            UseJitter = true,\n            Delay = TimeSpan.FromMilliseconds(500)\n        });\n\n        pipeline.AddTimeout(TimeSpan.FromSeconds(1));\n    });\n</code></pre>\n<p>The <code>custom</code> policy won't be applied because we have a global resilience pipeline that overrides it.</p>\n<p>This is a big oversight in the current implementation of the .NET resilience packages.</p>\n<h2>The Solution</h2>\n<p>The solution is to create an extension method that clears all handlers from the resilience pipeline.\nThis allows you to remove the default handlers and add your custom ones.</p>\n<p>Here's how to implement it:</p>\n<pre><code class=\"language-csharp\">public static class ResilienceHttpClientBuilderExtensions\n{\n    public static IHttpClientBuilder RemoveAllResilienceHandlers(this IHttpClientBuilder builder)\n    {\n        builder.ConfigureAdditionalHttpMessageHandlers(static (handlers, _) =&gt;\n        {\n            for (int i = handlers.Count - 1; i &gt;= 0; i--)\n            {\n                if (handlers[i] is ResilienceHandler)\n                {\n                    handlers.RemoveAt(i);\n                }\n            }\n        });\n        return builder;\n    }\n}\n</code></pre>\n<p>Now you can use this extension method to implement custom resilience strategies:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddHttpClient(&quot;github&quot;)\n    .ConfigureHttpClient(client =&gt;\n    {\n        client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n    })\n    .RemoveAllResilienceHandlers()\n    .AddResilienceHandler(&quot;custom&quot;, pipeline =&gt;\n    {\n        // Configure the custom resilience pipeline...\n    });\n\n// Or use another standard resilience pipeline...\nbuilder.Services\n    .AddHttpClient(&quot;github-hedged&quot;)\n    .RemoveAllResilienceHandlers()\n    .AddStandardHedgingHandler();\n</code></pre>\n<h2>Future Improvements</h2>\n<p>The .NET team is aware of this limitation, and better support for overriding default resilience handlers is planned for an upcoming release.\nThe <a href=\"https://github.com/dotnet/extensions/pull/5801\">pull request for this API</a> is merged and should be available in a future release.</p>\n<p>Until then, this workaround using <code>RemoveAllResilienceHandlers</code> is a drop-in replacement for the missing feature.</p>\n<h2>Conclusion</h2>\n<p>The ability to override default resilience handlers is much needed when building robust distributed systems.\nWhile .NET's standard resilience handlers provide excellent defaults, real-world applications often require fine-tuned resilience strategies for different services.\nThe extension method presented here bridges this gap, allowing you to maintain both global defaults and specialized configurations where needed.</p>\n<p>Want to dive deeper into building resilient cloud applications?\nCheck out my article about <a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\"><strong>building resilient cloud applications with .NET</strong></a>.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/overriding-default-http-resilience-handlers-in-dotnet",
            "title": "Overriding Default HTTP Resilience Handlers in .NET",
            "summary": "Configure the standard resilience handler globally and it overrides the custom pipeline you added for a single HttpClient.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_127.png",
            "date_modified": "2025-02-01T00:00:00.000Z",
            "date_published": "2025-02-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-aes-encryption-with-csharp",
            "content_html": "<p>AES encryption in C# starts with <code>Aes.Create()</code>, a 256-bit key size, and a fresh random key and IV for every operation.\nEncrypt through a <code>CryptoStream</code>, then store the IV with the ciphertext and keep the key in a key management service such as Azure Key Vault.</p>\n<p>A single exposed API key or database password can compromise your entire infrastructure.\nYet many developers still store sensitive data with basic encoding or weak encryption.</p>\n<p>Properly implemented encryption is your last line of defense.\nWhen other security measures fail, it ensures stolen data remains unreadable.\nThis is especially crucial for API keys, database credentials, and user secrets that grant direct access to your systems.</p>\n<p>In today's issue, we will cover implementing AES encryption in .NET with practical code examples and essential security considerations.</p>\n<h2>Symmetric vs Asymmetric Encryption</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Symmetric-key_algorithm\">Symmetric encryption</a> (like AES) uses the same key for encryption and decryption.\nIt's fast and ideal for storing data that only your application needs to read.\nThe main challenge is securely storing the encryption key.</p>\n<p><a href=\"https://en.wikipedia.org/wiki/Public-key_cryptography\">Asymmetric encryption</a> (like RSA) uses different keys for encryption and decryption.\nIt's slower but allows secure communication between parties who don't share secrets.\nCommon uses include SSL/TLS and digital signatures.</p>\n<p>For storing API keys and application secrets, symmetric encryption with AES is the appropriate choice.</p>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_126/aes_encryption.png\" alt=\"AES encryption algorithm.\">\n  <figcaption>AES encryption and decryption process block diagram.</figcaption>\n</figure>\n<h2>AES Encryption Implementation</h2>\n<p>Let's examine a secure <a href=\"https://en.wikipedia.org/wiki/Advanced_Encryption_Standard\">AES (Advanced Encryption Standard)</a> encryption implementation in C#.\nThis implementation uses AES-256, which provides the strongest security currently available in the AES standard.</p>\n<pre><code class=\"language-csharp\">public class Encryptor\n{\n    private const int KeySize = 256;\n    private const int BlockSize = 128;\n\n    public static EncryptionResult Encrypt(string plainText)\n    {\n        // Generate a random key and IV\n        using var aes = Aes.Create();\n        aes.KeySize = KeySize;\n        aes.BlockSize = BlockSize;\n\n        // Generate a random key and IV for each encryption operation\n        aes.GenerateKey();\n        aes.GenerateIV();\n\n        byte[] encryptedData;\n\n        // Create encryptor and encrypt the data\n        using (var encryptor = aes.CreateEncryptor())\n        using (var msEncrypt = new MemoryStream())\n        {\n            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))\n            using (var swEncrypt = new StreamWriter(csEncrypt))\n            {\n                swEncrypt.Write(plainText);\n            }\n\n            encryptedData = msEncrypt.ToArray();\n        }\n\n        // Package everything together, storing IV with the encrypted data\n        var result = EncryptionResult.CreateEncryptedData(\n            encryptedData,\n            aes.IV,\n            Convert.ToBase64String(aes.Key)\n        );\n\n        return result;\n    }\n}\n\npublic class EncryptionResult\n{\n    // The IV is prepended to the encrypted data\n    public string EncryptedData { get; set; }\n    public string Key { get; set; }\n\n    public static EncryptionResult CreateEncryptedData(byte[] data, byte[] iv, string key)\n    {\n        // Combine IV and encrypted data\n        var combined = new byte[iv.Length + data.Length];\n        Array.Copy(iv, 0, combined, 0, iv.Length);\n        Array.Copy(data, 0, combined, iv.Length, data.Length);\n\n        return new EncryptionResult\n        {\n            EncryptedData = Convert.ToBase64String(combined),\n            Key = key\n        };\n    }\n\n    public (byte[] iv, byte[] encryptedData) GetIVAndEncryptedData()\n    {\n        var combined = Convert.FromBase64String(EncryptedData);\n\n        // Extract IV and data\n        var iv = new byte[16]; // AES block size is 16 bytes (128 / 8)\n        var encryptedData = new byte[combined.Length - 16];\n\n        Array.Copy(combined, 0, iv, 0, 16);\n        Array.Copy(combined, 16, encryptedData, 0, encryptedData.Length);\n\n        return (iv, encryptedData);\n    }\n}\n</code></pre>\n<p>Let's break down what's happening in this implementation:</p>\n<ul>\n<li>Every encryption operation generates a new random key and IV (Initialization Vector).\nThis is crucial - reusing either of these compromises security.\nThe IV prevents identical plaintext from producing identical ciphertext.</li>\n<li>We use <code>CryptoStream</code> for efficient encryption of potentially large data.\nThe stream pattern ensures we don't load everything into memory at once.</li>\n<li>The <code>EncryptionResult</code> class provides a way to package the encrypted data with its key and IV.\nIn production, the key should be stored separately in a key management service.</li>\n</ul>\n<h2>AES Decryption Implementation</h2>\n<p>Here's the corresponding decryption implementation:</p>\n<pre><code class=\"language-csharp\">public class Decryptor\n{\n    private const int KeySize = 256;\n    private const int BlockSize = 128;\n\n    public static string Decrypt(EncryptionResult encryptionResult)\n    {\n        var key = Convert.FromBase64String(encryptionResult.Key);\n        var (iv, encryptedData) = encryptionResult.GetIVAndEncryptedData();\n\n        using var aes = Aes.Create();\n        aes.KeySize = KeySize;\n        aes.BlockSize = BlockSize;\n        aes.Key = key;\n        aes.IV = iv;\n\n        // Create decryptor and decrypt the data\n        using var decryptor = aes.CreateDecryptor();\n        using var msDecrypt = new MemoryStream(encryptedData);\n        using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);\n        using var srDecrypt = new StreamReader(csDecrypt);\n\n        try\n        {\n            return srDecrypt.ReadToEnd();\n        }\n        catch (CryptographicException ex)\n        {\n            // Log the error securely - avoid exposing details\n            throw new CryptographicException(&quot;Decryption failed&quot;, ex);\n        }\n    }\n}\n</code></pre>\n<p>The decryption process reverses the encryption steps.\nNote the error handling - we catch cryptographic exceptions but avoid exposing details that could help an attacker.\nIn production, you should log these errors securely for debugging while keeping security in mind.</p>\n<h2>Usage Example</h2>\n<p>Here's an example of encrypting and decrypting sensitive data using the implementations above:</p>\n<pre><code class=\"language-csharp\">// Encrypt sensitive data\nvar apiKey = &quot;your-sensitive-api-key&quot;;\nvar encryptionResult = Encryptor.Encrypt(apiKey);\n\n// Output example: DCGT9kEwPglBonWWPa7PQPbr2I+6rskJ0lSFybbicvZ+wKMTU7cbJD2s3QSF2Yu6\n\n// Store encrypted data in database\n// IV is stored with the encrypted data\nSaveToDatabase(encryptionResult.EncryptedData);\n\n// Store key in key vault\nawait keyVault.StoreKeyAsync(&quot;apikey_1&quot;, encryptionResult.Key);\n\n// Later, decrypt when needed\n// IV is retrieved from the encrypted data\nvar encryptedData = LoadFromDatabase();\nvar key = await keyVault.GetKeyAsync(&quot;apikey_1&quot;);\n\nvar result = new EncryptionResult\n{\n    EncryptedData = encryptedData,\n    Key = key,\n    IV = iv\n};\n\nvar decrypted = Decryptor.Decrypt(result);\n</code></pre>\n<h2>Takeaway</h2>\n<p>AES encryption provides strong security for sensitive application data when implemented correctly.</p>\n<p>Proper key management is very important.\nUse a dedicated key storage service in production.\nPopular options include <a href=\"https://learn.microsoft.com/en-us/azure/key-vault/\">Azure Key Vault</a>,\n<a href=\"https://aws.amazon.com/kms/\">AWS Key Management Service</a>, and\n<a href=\"https://www.vaultproject.io/\">HashiCorp Vault</a>.</p>\n<p>In my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>Pragmatic REST APIs</strong></a> course, I cover secure data storage and encryption in more detail.\nThese are critical aspects of building secure and robust APIs and integrating with third-party APIs.\nCheck it out if you're interested in learning more.</p>\n<p>Remember that encryption is just one part of a comprehensive security strategy.\nKeep your encryption keys separate from encrypted data and rotate them regularly.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-aes-encryption-with-csharp",
            "title": "Implementing AES Encryption With C#",
            "summary": "Learn how to implement secure AES encryption in C# to protect sensitive application data like API keys and passwords, with practical code examples covering…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_126.png",
            "date_modified": "2025-01-25T00:00:00.000Z",
            "date_published": "2025-01-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/scaling-monoliths-a-practical-guide-for-growing-systems",
            "content_html": "<p>A well-designed monolith scales well without moving to microservices.\nStart by tuning queries and adding caching, scale the machine up while that stays cost-effective, then run several stateless instances behind a load balancer.\nDatabase pressure goes to read replicas, materialized views, and eventually sharding, with message queues absorbing traffic spikes.</p>\n<p>Monoliths get a bad rap in our industry.\nWe're told they're legacy, that they don't scale, and that we need <a href=\"https://milanjovanovic.tech/blog/modular-monolith-vs-microservices\"><strong>microservices</strong></a> to succeed.\nAfter spending many years scaling systems from startups to enterprises, I can tell you this isn't true.\nA well-designed monolith is often the right architecture choice, and it can scale remarkably well with the right approach.</p>\n<p>In my experience building and scaling monolithic systems, I've found that the key to success isn't following trends.\nIt's understanding your scaling needs and applying the right solutions at the right time.\nIn this article, I'll share what I've learned about scaling monoliths effectively and when to use each approach.</p>\n<h2>Understanding Scale</h2>\n<p>A monolith puts all your code in one deployable unit.\nThis brings significant advantages: faster development cycles, simpler debugging, and straightforward deployments.\nBut as your system grows, you'll face scaling challenges.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_125/monolith_system.png\" alt=\"Simple monolith system.\">\n</div>\n<p>Your database queries slow down as data volume grows. API endpoints that worked\nfine with hundreds of users start timing out with thousands. Build times creep\nup as your codebase expands. These are natural growing pains that every\nsuccessful system faces.</p>\n<h2>Vertical Scaling</h2>\n<p>Vertical scaling means giving your application more resources on a single machine.\nIt's the simplest scaling strategy and often the most effective first step.\nBefore diving into complex distributed systems, consider whether upgrading your existing infrastructure could solve your performance problems.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_125/vertical_scaling.png\" alt=\"Example of vertically scaling a monolith.\">\n</div>\n<p>Vertical scaling works particularly well when there are clear resource bottlenecks.\nIf your CPU is consistently above 80% utilization, adding more cores will help.\nIf your database is I/O bound, upgrading to faster storage can dramatically improve performance.\nModern cloud platforms make this especially straightforward.\nYou can often upgrade with a few clicks and minimal downtime.</p>\n<p>The benefits of vertical scaling extend beyond just performance.\nIt maintains your system's simplicity.\nYou don't need to redesign your architecture, implement new deployment patterns, or manage distributed system complexity.\nYour monitoring, debugging, and operational procedures all stay the same.</p>\n<p>However, vertical scaling does have limits.\nYou'll eventually hit a ceiling on what a single machine can handle.\nCloud providers have maximum instance sizes, and costs typically increase exponentially with larger instances.\nMore importantly, vertical scaling doesn't provide redundancy - you're still running on a single machine that represents a potential single point of failure.</p>\n<p>Knowing when to move beyond vertical scaling is crucial.\nWatch for these indicators:</p>\n<ul>\n<li>Costs are growing faster than your user base</li>\n<li>You need better redundancy and fault tolerance</li>\n<li>Your deployment downtime is impacting business operations</li>\n<li>Your largest available instance size is approaching 70% utilization</li>\n</ul>\n<h2>Horizontal Scaling</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing\"><strong>Horizontal scaling</strong></a> runs multiple instances of your application behind a load balancer.\nIt's the next step when vertical scaling reaches its limits.\nIt offers improved fault tolerance and nearly linear scaling capabilities.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_125/horizontal_scaling.png\" alt=\"Example of horizontally scaling a monolith.\">\n</div>\n<p>The key to successful horizontal scaling lies in application design.\nYour application must be stateless - each request should contain all the information needed to process it.</p>\n<p>This means:</p>\n<ul>\n<li>Authentication should use tokens (like <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWTs</strong></a>) rather than server-side sessions</li>\n<li>Cached data should live in a distributed cache</li>\n</ul>\n<p>Load balancers are crucial in this architecture.\nWhether you call it an <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>API Gateway</strong></a>, Reverse Proxy, or Load Balancer, its job is to distribute traffic across your application instances.\nPopular choices include:</p>\n<ul>\n<li><a href=\"https://nginx.org/en/\">nginx</a>: Powerful, open-source, great for custom configurations</li>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/\">YARP</a>: Microsoft's .NET reverse proxy, great for .NET applications</li>\n<li>Cloud:\n<a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html\">AWS ALB</a>,\n<a href=\"https://learn.microsoft.com/en-us/azure/application-gateway/overview\">Azure Application Gateway</a>,\n<a href=\"https://cloud.google.com/load-balancing?hl=en\">Google Cloud Load Balancing</a></li>\n</ul>\n<p>Horizontal scaling provides several key benefits:</p>\n<ul>\n<li>Better fault tolerance through redundancy</li>\n<li>Ability to handle more concurrent users</li>\n<li>Rolling deployments with zero downtime</li>\n<li>Cost-effective scaling (scale down when traffic is low)</li>\n</ul>\n<p>The main challenge in horizontal scaling isn't technical—it's architectural.\nYour application needs to be designed for horizontal scaling from the start.\nConverting a stateful application to a stateless one often requires significant refactoring.</p>\n<h2>Database Scaling</h2>\n<p>Database scaling is where most monoliths first hit real limitations.\nLet's explore each scaling strategy in detail.</p>\n<h3>Read Replicas</h3>\n<p>Read replicas are often your first step in database scaling but come with significant trade-offs.\nRead replicas maintain a copy of your primary database that serves read-only traffic.\nWhen you run a query against a replica, you're not competing with writes on your primary database.</p>\n<p>Each replica maintains an up-to-date copy of your data through replication.\nChanges flow one-way: from primary to replicas.\nThis means any data written to your primary will eventually show up in your replicas.\nThat &quot;eventually&quot; is important - you're trading consistency for better read performance.</p>\n<p>Most cloud providers make read replicas easy to set up.\nAWS RDS and Azure SQL all support read replicas with minimal configuration.\nThey handle replication, monitoring, and failover for you.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_125/database_read_replicas.png\" alt=\"Example of database read replication for scaling a monolith.\">\n<p>When implementing read replicas, consider:</p>\n<ul>\n<li>Replication lag affects data freshness</li>\n<li>Write volume impacts replication speed</li>\n<li>Geographic location affects latency</li>\n<li>Each replica adds to your costs</li>\n</ul>\n<h3>Materialized Views</h3>\n<p>Sometimes read replicas aren't enough.\nPerhaps you need to reshape your data for specific use cases, or you're running complex analytical queries that are slow even on a replica.\nThis is where materialized views come in.</p>\n<p>A materialized view is a pre-computed dataset stored as a table.\nUnlike regular views that compute their results on each query, materialized views store their results.\nThis makes them much faster to query but introduces a new challenge: keeping them up to date.</p>\n<p>Materialized views excel at:</p>\n<ul>\n<li>Complex analytical queries</li>\n<li>Data that updates on a schedule</li>\n<li>Aggregations and summaries</li>\n<li>Denormalized data for specific views</li>\n</ul>\n<p>The key trade-off is freshness versus performance.\nYou need to decide how often to refresh your materialized views.\nToo often, and you're putting load on your database.\nToo rarely, and your data gets stale.</p>\n<h3>Database Sharding</h3>\n<p>Sharding becomes necessary when your database grows beyond what a single instance can handle.\nSharding splits your data across multiple database instances, with each shard containing a distinct subset of your data.\nThe key to successful sharding lies in choosing the right sharding strategy for your use case.</p>\n<p><strong>Range-based sharding</strong> splits data based on ranges of a key value - for example, customers A-M go to Shard 1, N-Z to Shard 2.\nThis approach works well with data that has a natural range distribution, like dates or alphabetical order,\nbut can lead to hotspots if certain ranges see more activity than others.</p>\n<p><strong>Hash-based sharding</strong> applies a hash function to your sharding key to determine which shard holds the data.\nThe choice of hashing function is crucial.\nIt must distribute data evenly across your shards to prevent any single shard from becoming a bottleneck.\nWhile this approach provides better data distribution, it makes range-based queries more complex since related data might live on different shards.</p>\n<p><strong>Tenant-based sharding</strong> gives each tenant their own database.\nThis approach provides natural isolation and makes tenant-specific operations straightforward.\nWhile it makes cross-tenant queries more complex, it's often the cleanest solution for multi-tenant systems where data isolation is important.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_125/database_sharding.png\" alt=\"Example of database sharding for scaling a monolith.\">\n<h2>Caching</h2>\n<p>Caching is one of the most effective ways to improve your system's performance.\nA well-implemented caching strategy can dramatically reduce database load and improve response times by storing frequently accessed data in memory.</p>\n<p>Modern caching happens at multiple levels.\nBrowser caching reduces unnecessary network requests.\nCDN caching brings your content closer to users.\n<a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>Application-level caching</strong></a> with tools like Redis stores frequently accessed data in memory.\nDatabase query caching reduces expensive computations.</p>\n<p>The key to effective caching is understanding your data access patterns.\nFrequently read, rarely changed data benefits most from caching.\nTools like Redis and Memcached excel at storing such data in memory, providing sub-millisecond access times.\nCloud providers offer managed caching services like Azure Cache for Redis,\nhandling the operational complexity of maintaining a distributed cache.</p>\n<h2>Message Queues</h2>\n<p>Message queues are a powerful tool for scaling your monolith.\nThey let you defer time-consuming operations and distribute work across multiple processors.\nThis keeps your API responsive while handling heavy tasks in the background.</p>\n<p>Message queues transform your system's behavior under load.\nInstead of processing everything synchronously, you can queue work for later.\nThis pattern works especially well for operations like:</p>\n<ul>\n<li>Processing uploaded files</li>\n<li>Sending emails and notifications</li>\n<li>Generating reports</li>\n<li>Updating search indexes</li>\n<li>Running batch operations</li>\n</ul>\n<p>The real power of message queues lies in their ability to handle traffic spikes.\nWhen your system gets hit with a surge of requests, queues act as a buffer.\nThey let you accept work at peak rates but process it at a sustainable pace.</p>\n<h2>Summary</h2>\n<p>Scaling a monolith isn't about choosing between vertical scaling, horizontal scaling, caching, or any single approach.\nIt's about using the right tool at the right time.\nStart with the simplest solution that solves your immediate problem, then add complexity only when needed.</p>\n<p>A practical scaling journey often looks like this:</p>\n<ol>\n<li>Optimize your code and database queries</li>\n<li>Add caching where it matters most</li>\n<li>Scale vertically until it's no longer cost-effective</li>\n<li>Move to horizontal scaling for better redundancy</li>\n<li>Implement message queues for background work</li>\n<li>Consider database sharding when data size demands it</li>\n</ol>\n<p>A well-designed monolith can handle significant load with just a subset of these techniques.\nThe key is to understand your system's actual bottlenecks and address them specifically.</p>\n<p>If you're interested in building maintainable, scalable monoliths, my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course dives deeper into these concepts.\nYou'll learn how to structure your code for long-term maintainability and scale.</p>\n<p>Remember: don't let perfect be the enemy of good.\nStart with the simplest solution that could work, measure everything, and scale what's necessary.\nA well-designed monolith can take you further than you might think.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/scaling-monoliths-a-practical-guide-for-growing-systems",
            "title": "Scaling Monoliths: A Practical Guide for Growing Systems",
            "summary": "A well-designed monolith can scale remarkably well, despite industry trends pushing toward microservices.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_125.png",
            "date_modified": "2025-01-18T00:00:00.000Z",
            "date_published": "2025-01-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai",
            "content_html": "<p><code>Microsoft.Extensions.AI</code> gives .NET a single <code>IChatClient</code> abstraction over LLM providers, so the same code runs against Ollama, Azure OpenAI, or OpenAI.\nYou register a client with <code>AddChatClient</code> and call <code>GetResponseAsync</code>, with a generic overload that deserializes the reply into a C# class.\nOllama in Docker covers local development.</p>\n<p>I've been experimenting with different approaches to integrating LLMs into .NET apps,\nand I want to share what I've learned about using <code>Microsoft.Extensions.AI</code>.</p>\n<p>Large Language Models (LLMs) have revolutionized how we approach AI-powered applications.\nWhile many developers are familiar with cloud-based solutions like OpenAI's GPT models,\nrunning LLMs locally has become increasingly accessible thanks to projects like <a href=\"https://ollama.com/\">Ollama</a>.</p>\n<p>In this article, we'll explore how to use LLMs in .NET applications using <code>Microsoft.Extensions.AI</code>,\na powerful abstraction that extends the <a href=\"https://github.com/microsoft/semantic-kernel\">Semantic Kernel</a> SDK.</p>\n<h2>Understanding the Building Blocks</h2>\n<h3>Large Language Models (LLMs)</h3>\n<p>LLMs are deep learning models trained on vast amounts of data, capable of understanding and generating human-like text.\nThese models can perform various tasks such as text completion, summarization, classification, and engaging in conversation.\nWhile traditionally accessed through cloud APIs, recent advances have made it possible to run them locally on standard hardware.</p>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_124/large_language_models.png\" alt=\"Timeline of large language models.\">\n  <figcaption>\n    Source: <a href=\"https://wandb.ai/vincenttu/blog_posts/reports/A-Survey-of-Large-Language-Models--VmlldzozOTY2MDM1\">Weights &amp;\nBiases</a>\n  </figcaption>\n</figure>\n<h3>Ollama</h3>\n<p>Ollama is an open-source project that simplifies <a href=\"https://milanjovanovic.tech/blog/how-to-extract-structured-data-from-images-using-ollama-in-dotnet\"><strong>running LLMs locally</strong></a>.\nIt provides a Docker container that can run various open-source models like Llama,\nmaking it easy to experiment with AI without depending on cloud services.\nOllama handles model management and optimization and provides a simple API for interactions.</p>\n<h3>Microsoft.Extensions.AI</h3>\n<p><a href=\"https://www.nuget.org/packages/Microsoft.Extensions.AI\">Microsoft.Extensions.AI</a> is a library that provides a unified interface for working with LLMs in .NET applications.\nBuilt on top of Microsoft's Semantic Kernel, it abstracts away the complexity of different LLM implementations,\nallowing developers to switch between providers (like Ollama, Azure, or OpenAI) without changing application code.</p>\n<h2>Getting Started</h2>\n<p>Before diving into the examples, here's what you need to run LLMs locally:</p>\n<ol>\n<li>Docker running on your machine</li>\n<li>Ollama container running with the <code>llama3</code> model:</li>\n</ol>\n<pre><code class=\"language-bash\"># Pull the Ollama container\ndocker run --gpus all -d -v ollama_data:/root/.ollama -p 11434:11434 --name ollama ollama/ollama\n\n# Pull the llama3 model\ndocker exec -it ollama ollama pull llama3\n</code></pre>\n<ol start=\"3\">\n<li>A few NuGet packages (I built this using a .NET 9 console application):</li>\n</ol>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.Extensions.AI # The base AI library\nInstall-Package Microsoft.Extensions.AI.Ollama # Ollama provider implementation\nInstall-Package Microsoft.Extensions.Hosting # For building the DI container\n</code></pre>\n<h2>Simple Chat Completion</h2>\n<p>Let's start with a basic example of chat completion.\nHere's the minimal setup:</p>\n<pre><code class=\"language-csharp\">var builder = Host.CreateApplicationBuilder();\n\nbuilder.Services.AddChatClient(new OllamaChatClient(new Uri(&quot;http://localhost:11434&quot;), &quot;llama3&quot;));\n\nvar app = builder.Build();\n\nvar chatClient = app.Services.GetRequiredService&lt;IChatClient&gt;();\n\nvar response = await chatClient.GetResponseAsync(&quot;What is .NET? Reply in 50 words max.&quot;);\n\nConsole.WriteLine(response.Message.Text);\n</code></pre>\n<p>Nothing fancy here - we're just setting up dependency injection and asking a simple question.\nIf you're used to using raw API calls, you'll notice how clean this feels.</p>\n<p>The <code>AddChatClient</code> extension method registers the chat client with the DI container.\nThis allows you to inject <code>IChatClient</code> into your services and interact with LLMs using a simple API.\nThe implementation uses the <code>OllamaChatClient</code> to communicate with the Ollama container running locally.</p>\n<h2>Implementing Chat with History</h2>\n<p>Building on the previous example, we can create an interactive chat that maintains conversation history.\nThis is useful for context-aware interactions and real-time chat applications.\nAll we need is a <code>List&lt;ChatMessage</code> to store the chat history:</p>\n<pre><code class=\"language-csharp\">var chatHistory = new List&lt;ChatMessage&gt;();\n\nwhile (true)\n{\n   Console.WriteLine(&quot;Enter your prompt:&quot;);\n   var userPrompt = Console.ReadLine();\n   chatHistory.Add(new ChatMessage(ChatRole.User, userPrompt));\n\n   Console.WriteLine(&quot;Response from AI:&quot;);\n   var chatResponse = &quot;&quot;;\n   await foreach (var item in chatClient.GetStreamingResponseAsync(chatHistory))\n   {\n       // We're streaming the response, so we get each message as it arrives\n       Console.Write(item.Text);\n       chatResponse += item.Text;\n   }\n   chatHistory.Add(new ChatMessage(ChatRole.Assistant, chatResponse));\n   Console.WriteLine();\n}\n</code></pre>\n<p>The cool part here is the streaming response - you get that nice, gradual text appearance like in ChatGPT.\nWe're also maintaining chat history, which lets the model understand context from previous messages, making conversations feel more natural.</p>\n<h2>Getting Practical: Article Summarization</h2>\n<p>Let's try something more useful - automatically summarizing articles.\nI've been using this to process blog posts:</p>\n<pre><code class=\"language-csharp\">var posts = Directory.GetFiles(&quot;posts&quot;).Take(5).ToArray();\nforeach (var post in posts)\n{\n   string prompt = $$&quot;&quot;&quot;\n         You will receive an input text and the desired output format.\n         You need to analyze the text and produce the desired output format.\n         You not allow to change code, text, or other references.\n\n         # Desired response\n\n         Only provide a RFC8259 compliant JSON response following this format without deviation.\n\n         {\n            &quot;title&quot;: &quot;Title pulled from the front matter section&quot;,\n            &quot;summary&quot;: &quot;Summarize the article in no more than 100 words&quot;\n         }\n\n         # Article content:\n\n         {{File.ReadAllText(post)}}\n         &quot;&quot;&quot;;\n\n   var response = await chatClient.GetResponseAsync(prompt);\n   Console.WriteLine(response.Message.Text);\n   Console.WriteLine(Environment.NewLine);\n}\n</code></pre>\n<p>Pro tip: Being specific about the output format (like requesting <a href=\"https://datatracker.ietf.org/doc/html/rfc8259\">RFC8259</a> compliant JSON) helps get consistent results.\nI learned this the hard way after dealing with occasionally malformed responses!</p>\n<h2>Taking It Further: Smart Categorization</h2>\n<p>Here's where it gets really interesting - we can get strongly typed responses directly from our LLM:</p>\n<pre><code class=\"language-csharp\">class PostCategory\n{\n    public string Title { get; set; } = string.Empty;\n    public string[] Tags { get; set; } = [];\n}\n\nvar posts = Directory.GetFiles(&quot;posts&quot;).Take(5).ToArray();\nforeach (var post in posts)\n{\n    string prompt = $$&quot;&quot;&quot;\n          You will receive an input text and the desired output format.\n          You need to analyze the text and produce the desired output format.\n          You not allow to change code, text, or other references.\n\n          # Desired response\n\n          Only provide a RFC8259 compliant JSON response following this format without deviation.\n\n          {\n             &quot;title&quot;: &quot;Title pulled from the front matter section&quot;,\n             &quot;tags&quot;: &quot;Array of tags based on analyzing the article content. Tags should be lowercase.&quot;\n          }\n\n          # Article content:\n\n          {{File.ReadAllText(post)}}\n          &quot;&quot;&quot;;\n\n    var response = await chatClient.GetResponseAsync&lt;PostCategory&gt;(prompt);\n\n    Console.WriteLine(\n      $&quot;{response.Result.Title}. Tags: {string.Join(&quot;,&quot;,response.Result.Tags)}&quot;);\n}\n</code></pre>\n<p>The strongly typed approach provides compile-time safety and better IDE support, making it easier to maintain and refactor code that interacts with LLM responses.</p>\n<h2>Flexibility with Different LLM Providers</h2>\n<p>One of the key advantages of <code>Microsoft.Extensions.AI</code> is support for different providers.\nWhile our examples use Ollama, you can easily switch to other providers:</p>\n<pre><code class=\"language-csharp\">// Using Azure OpenAI\nbuilder.Services.AddChatClient(new AzureOpenAIClient(\n        new Uri(&quot;AZURE_OPENAI_ENDPOINT&quot;),\n        new DefaultAzureCredential())\n            .AsChatClient());\n\n// Using OpenAI\nbuilder.Services.AddChatClient(new OpenAIClient(&quot;OPENAI_API_KEY&quot;).AsChatClient());\n</code></pre>\n<p>This flexibility allows you to:</p>\n<ul>\n<li>Start development with local models</li>\n<li>Move to production with cloud providers</li>\n<li>Switch between providers without changing application code</li>\n<li>Mix different providers for different use cases (categorization, image recognition, etc.)</li>\n</ul>\n<h2>Takeaway</h2>\n<p><code>Microsoft.Extensions.AI</code> makes it very simple to integrate LLMs into .NET applications.\nWhether you're building a chat interface, processing documents, or adding AI-powered features to your application,\nthe library provides a clean, consistent API that works across different LLM providers.</p>\n<p>I've only scratched the surface here.\nSince integrating this into my projects, I've found countless uses:</p>\n<ul>\n<li>Automated content moderation for user submissions</li>\n<li>Automated support ticket categorization</li>\n<li>Content summarization for newsletters</li>\n</ul>\n<p>I'm also planning a small side project that will use LLMs to process images from a camera feed.\nThe idea is to detect anything unusual and trigger alerts in real-time.</p>\n<p>What are you planning to build with this?\nI'd love to hear about your projects and experiences.\nThe AI space is moving fast, but with tools like <code>Microsoft.Extensions.AI</code>, we can focus on building features rather than wrestling with infrastructure.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/working-with-llms-in-dotnet-using-microsoft-extensions-ai",
            "title": "Working with LLMs in .NET using Microsoft.Extensions.AI",
            "summary": "Microsoft.Extensions.AI gives you one interface for LLMs, so you can switch between Ollama, Azure, and OpenAI without changing application code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_124.png",
            "date_modified": "2025-01-11T00:00:00.000Z",
            "date_published": "2025-01-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/unit-testing-clean-architecture-use-cases",
            "content_html": "<p>Unit testing a Clean Architecture use case means mocking every dependency the handler takes, then asserting on the <code>Result</code> it returns.\nCover the failure paths, the happy path, and exception propagation, using Arrange-Act-Assert and descriptive test names.\nA mock cannot prove that the real query logic works, which is what integration tests are for.</p>\n<p>Writing tests is a crucial part of my daily work.\nOver the years, I've learned that good tests can make or break a project.</p>\n<p>One project I worked on remains the best example of this.\nIt was a large, complex system with many moving parts.\nWe had a requirement that code coverage must be above 90%.</p>\n<p>Code coverage doesn't directly translate to good tests, but it's a good starting point.\nIt's up to you to write quality tests that cover the most critical parts of your system.</p>\n<p>Today, I want to share my approach to testing Clean Architecture use cases in .NET.</p>\n<h2>Why Testing Matters</h2>\n<p>I've seen many projects fail because of poor testing practices.\nThe codebase grows, changes become risky, and developers lose confidence in their deployments.\nThis is especially true for <a href=\"https://milanjovanovic.tech/blog/clean-architecture-dotnet\"><strong>Clean Architecture</strong></a> projects, where we need to ensure our use cases work correctly.</p>\n<p>Testing isn't just about catching bugs.\nIt's about having confidence in your code.\nWhen I make changes, I want to know immediately if I've broken something.\nGood tests give me that confidence.</p>\n<h2>Understanding Different Testing Approaches</h2>\n<p>Before diving into the specific examples, let's talk about testing types.\nIn my experience, there are three main types of tests you'll write:</p>\n<ol>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>Unit tests</strong></a> focus on testing individual components in isolation.\nThey're fast, reliable, and help you catch issues early.\nI write these tests first, and they make up the majority of my test suite.</p>\n</li>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>Integration tests</strong></a> verify that different components work together correctly.\nThey're slower but essential for testing database operations or external services.</p>\n</li>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing\"><strong>End-to-end tests</strong></a> check the entire system flow.\nThey're the slowest but provide confidence that everything works together.</p>\n</li>\n</ol>\n<p>For this article, we'll focus on unit tests. They're the foundation of a solid test suite and the most common type you'll write.</p>\n<h2>Breaking Down Our Use Case</h2>\n<p>Looking at our <code>ReserveBookingCommandHandler</code> class, we have a typical Clean Architecture use case.\nIt handles apartment booking reservations with several business rules:</p>\n<ol>\n<li>The apartment must exist</li>\n<li>The booking dates must not overlap with existing bookings</li>\n<li>A new booking should be created if all checks pass</li>\n</ol>\n<p>This is a perfect example for unit testing because it has clear inputs, outputs, and dependencies we can mock.</p>\n<pre><code class=\"language-csharp\">internal sealed class ReserveBookingCommandHandler(\n    IApartmentRepository apartmentRepository,\n    IBookingRepository bookingRepository,\n    IDateTimeProvider dateTimeProvider) : ICommandHandler&lt;ReserveBookingCommand, Guid&gt;\n{\n    public async Task&lt;Result&lt;Guid&gt;&gt; Handle(\n        ReserveBookingCommand request,\n        CancellationToken cancellationToken)\n    {\n        var apartment = await apartmentRepository.GetByIdAsync(request.ApartmentId, cancellationToken);\n\n        if (apartment is null)\n        {\n            return Result.Failure&lt;Guid&gt;(ApartmentErrors.NotFound);\n        }\n\n        var duration = DateRange.Create(request.StartDate, request.EndDate);\n\n        if (await bookingRepository.IsOverlappingAsync(apartment, duration, cancellationToken))\n        {\n            return Result.Failure&lt;Guid&gt;(BookingErrors.Overlap);\n        }\n\n        var booking = Booking.Create(\n            apartment,\n            duration,\n            dateTimeProvider.UtcNow);\n\n        bookingRepository.Add(booking);\n\n        return booking.Id;\n    }\n}\n</code></pre>\n<h2>Setting Up Our Test Environment</h2>\n<p>The test class setup shows the standard approach I use for all my handler tests. Let's break it down:</p>\n<pre><code class=\"language-csharp\">public class ReserveBookingCommandHandlerTests\n{\n    private readonly ReserveBookingCommandHandler _handler;\n    private readonly IApartmentRepository _apartmentRepository;\n    private readonly IBookingRepository _bookingRepository;\n    private readonly IDateTimeProvider _dateTimeProvider;\n\n    private static readonly Guid ApartmentId = Guid.NewGuid();\n    private static readonly DateTime UtcNow = DateTime.UtcNow;\n\n    public ReserveBookingCommandHandlerTests()\n    {\n        _apartmentRepository = Substitute.For&lt;IApartmentRepository&gt;();\n        _bookingRepository = Substitute.For&lt;IBookingRepository&gt;();\n        _dateTimeProvider = Substitute.For&lt;IDateTimeProvider&gt;();\n        _dateTimeProvider.UtcNow.Returns(UtcNow);\n\n        _handler = new ReserveBookingCommandHandler(\n            _apartmentRepository,\n            _bookingRepository,\n            _dateTimeProvider);\n    }\n}\n</code></pre>\n<p>I'm using <a href=\"https://nsubstitute.github.io/\">NSubstitute</a> to create mocks of our dependencies.\nEach test starts with fresh mocks, preventing test interference.\nThe static fields provide consistent values across all tests.</p>\n<p>Notice how I mock <code>IDateTimeProvider</code>.\nThis is crucial for testing time-dependent code.\nNever use <code>DateTime.UtcNow</code> directly in your production code - it makes testing much harder.</p>\n<h2>Testing the Not Found Scenario</h2>\n<p>Our first test verifies the behavior when an apartment doesn't exist:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Handle_WhenApartmentDoesNotExist_ShouldReturnNotFoundError()\n{\n    // Arrange\n    var command = new ReserveBookingCommand(\n        ApartmentId,\n        new DateOnly(2024, 1, 1),\n        new DateOnly(2024, 1, 5));\n\n    _apartmentRepository.GetByIdAsync(ApartmentId, Arg.Any&lt;CancellationToken&gt;())\n        .Returns((Apartment?)null);\n\n    // Act\n    var result = await _handler.Handle(command, default);\n\n    // Assert\n    result.IsFailure.Should().BeTrue();\n    result.Error.Should().Be(ApartmentErrors.NotFound);\n}\n</code></pre>\n<p>This test follows the <strong>Arrange-Act-Assert</strong> pattern:</p>\n<ol>\n<li>Arrange: Set up the command and mock the repository to return <code>null</code></li>\n<li>Act: Call the handler</li>\n<li>Assert: Verify we get the correct error</li>\n</ol>\n<p>I use <a href=\"https://fluentassertions.com/\">FluentAssertions</a> because it provides clear, readable assertions and better error messages than the standard Assert class.</p>\n<h2>Handling Booking Conflicts</h2>\n<p>The overlap test ensures we can't double-book apartments:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Handle_WhenBookingOverlaps_ShouldReturnOverlapError()\n{\n    // Arrange\n    var command = new ReserveBookingCommand(\n        ApartmentId,\n        new DateOnly(2024, 1, 1),\n        new DateOnly(2024, 1, 5));\n\n    var apartment = new Apartment { Id = ApartmentId };\n    _apartmentRepository.GetByIdAsync(ApartmentId, Arg.Any&lt;CancellationToken&gt;())\n        .Returns(apartment);\n    _bookingRepository.IsOverlappingAsync(apartment, Arg.Any&lt;DateRange&gt;(), Arg.Any&lt;CancellationToken&gt;())\n        .Returns(true);\n\n    // Act\n    var result = await _handler.Handle(command, default);\n\n    // Assert\n    result.IsFailure.Should().BeTrue();\n    result.Error.Should().Be(BookingErrors.Overlap);\n}\n</code></pre>\n<p>Here, we verify the overlap check works correctly.\nNotice how we:</p>\n<ol>\n<li>Mock the apartment repository to return a valid apartment</li>\n<li>Mock the booking repository to indicate an overlap</li>\n<li>Verify we get the overlap error</li>\n</ol>\n<h2>Testing Successful Bookings</h2>\n<p>The happy path test ensures everything works when all conditions are met:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Handle_WhenValidRequest_ShouldCreateBooking()\n{\n    // Arrange\n    var command = new ReserveBookingCommand(\n        ApartmentId,\n        new DateOnly(2024, 1, 1),\n        new DateOnly(2024, 1, 5));\n\n    var apartment = new Apartment { Id = ApartmentId };\n    _apartmentRepository.GetByIdAsync(ApartmentId, Arg.Any&lt;CancellationToken&gt;())\n        .Returns(apartment);\n    _bookingRepository.IsOverlappingAsync(apartment, Arg.Any&lt;DateRange&gt;(), Arg.Any&lt;CancellationToken&gt;())\n        .Returns(false);\n\n    // Act\n    var result = await _handler.Handle(command, default);\n\n    // Assert\n    result.IsSuccess.Should().BeTrue();\n    await _bookingRepository.Received(1)\n        .Add(Arg.Is&lt;Booking&gt;(b =&gt;\n            b.Id == result.Value &amp;&amp;\n            b.ApartmentId == ApartmentId));\n}\n</code></pre>\n<p>This test is more complex because we need to:</p>\n<ol>\n<li>Set up multiple mocks</li>\n<li>Verify the success result</li>\n<li>Check that the booking was added with correct properties</li>\n</ol>\n<p>NSubstitute's <code>Received()</code> method lets us verify the <code>Add</code> method was called <strong>exactly once</strong> with the right booking.</p>\n<h2>Verifying Exception Handling</h2>\n<p>Testing exception scenarios is crucial for robust code:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic async Task Handle_WhenRepositoryThrowsOverlapException_ShouldPropagateException()\n{\n    // Arrange\n    var command = new ReserveBookingCommand(\n        ApartmentId,\n        new DateOnly(2024, 1, 1),\n        new DateOnly(2024, 1, 5));\n\n    var apartment = new Apartment { Id = ApartmentId };\n    _apartmentRepository.GetByIdAsync(ApartmentId, Arg.Any&lt;CancellationToken&gt;())\n        .Returns(apartment);\n    _bookingRepository.IsOverlappingAsync(apartment, Arg.Any&lt;DateRange&gt;(), Arg.Any&lt;CancellationToken&gt;())\n        .Throws&lt;BookingOverlapException&gt;();\n\n    // Act\n    var act = () =&gt; _handler.Handle(command, default);\n\n    // Assert\n    await act.Should().ThrowAsync&lt;BookingOverlapException&gt;();\n}\n</code></pre>\n<p>This test ensures exceptions propagate correctly. We:</p>\n<ol>\n<li>Set up the scenario</li>\n<li>Make the repository throw an exception</li>\n<li>Verify the exception bubbles up</li>\n</ol>\n<p>FluentAssertions makes testing async exceptions clean and readable.</p>\n<h2>Understanding Test Coverage Limitations</h2>\n<p>When we look back at our booking overlap test, there's an important distinction to make.\nOur unit test verifies that our command handler behaves correctly when the booking repository reports an overlap.\nHowever, it doesn't verify that the overlap detection logic itself works correctly.</p>\n<p>Consider what we're actually testing:</p>\n<pre><code class=\"language-csharp\">_bookingRepository.IsOverlappingAsync(apartment, Arg.Any&lt;DateRange&gt;(), Arg.Any&lt;CancellationToken&gt;())\n    .Returns(true);\n</code></pre>\n<p>We're simply telling our mock repository to return <code>true</code>.\nThis gives us confidence that our command handler correctly handles the overlap scenario,\nbut it does not tell us whether our actual overlap detection logic works correctly.</p>\n<p>This is where integration tests become essential.\nAn integration test for this scenario would:</p>\n<ul>\n<li>Insert real bookings into a test database</li>\n<li>Attempt to create overlapping bookings</li>\n<li>Verify that the overlap detection works with real data</li>\n</ul>\n<p>The combination of unit and integration tests provides complete coverage:</p>\n<ul>\n<li>Unit tests verify the business logic flow</li>\n<li>Integration tests verify the actual overlap detection logic</li>\n</ul>\n<p>This example highlights why we need different types of tests.\nUnit tests are excellent for verifying behavior and logic flows,\nbut they can't verify the correctness of complex business rules that depend on real data interactions.</p>\n<h2>Summary</h2>\n<p>Unit testing Clean Architecture use cases requires careful thought about dependencies and behavior.\nHere are the key points:</p>\n<ul>\n<li>Mock all external dependencies</li>\n<li>Test both success and failure scenarios</li>\n<li>Verify exception handling logic</li>\n<li>Use descriptive test names</li>\n<li>Follow the Arrange-Act-Assert pattern</li>\n</ul>\n<p>Good tests act as documentation.\nThey show how the code should behave and catch issues before they reach production.\nInvest time in writing good tests - your future self will thank you.</p>\n<p>Want to dive deeper into testing Clean Architecture applications?\nI cover this and much more in my <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> course.\nYou'll learn how to write effective unit tests, integration tests, and end-to-end tests that give you real confidence in your system.</p>\n<p>Writing quality tests is a skill that improves with practice and understanding.\nTake the time to write them well.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/unit-testing-clean-architecture-use-cases",
            "title": "Unit Testing Clean Architecture Use Cases",
            "summary": "A project I worked on required code coverage above 90%, which taught me that coverage is a starting point and not a measure of test quality.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_123.png",
            "date_modified": "2025-01-04T00:00:00.000Z",
            "date_published": "2025-01-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/what-rewriting-a-40-year-old-project-taught-me-about-software-development",
            "content_html": "<p>To rewrite a legacy system without downtime, migrate gradually while both systems run side by side.\nWe kept the legacy system as the source of truth for everything not yet migrated, synced data both ways, and used RabbitMQ for message-based integration so modules could move over one at a time.\nStart with the core domain instead of peripheral quick wins, because postponing the core means every migrated feature needs complex synchronization with the legacy system.</p>\n<p>&quot;Your task is to rewrite this system. It powers our entire operation. Oh, and it's written in APL.&quot;</p>\n<p>That's how my journey with this legacy rewrite began.\nFor those unfamiliar with APL&gt;),\nit's a programming language from the 1960s known for its unique mathematical notation and array manipulation capabilities.\nFinding developers who know APL today is about as easy as finding a floppy disk drive in a modern computer.</p>\n<p>The system has grown over four decades.\nIt started as a simple inventory management tool and evolved into a comprehensive <a href=\"https://en.wikipedia.org/wiki/Enterprise_resource_planning\">ERP</a> system.\nMore than 460+ database tables.\nCountless business rules embedded in the code.\nComplex integrations with every part of the business process.\nThe system is the backbone of a manufacturing operation, that generates over $10 million in annual revenue.</p>\n<p>Our mission was clear but daunting: modernize this system using .NET, PostgreSQL, and React.</p>\n<p>The catch?\nThe business needed to keep running during the transition.\nNo downtime.\nNo data loss.\nNo disruption to daily operations.</p>\n<p>This wasn't just a technical challenge.\nIt was a lesson in managing complexity, understanding legacy business processes, and navigating organizational dynamics.</p>\n<p>So here's that story and the lessons learned.</p>\n<h2>Initial State: Understanding the Legacy</h2>\n<p>The first challenge was understanding how this massive system actually worked.\nThe codebase had grown organically over four decades, maintained by a single development team.\nThey were now in their 60s and looking to retire.</p>\n<p>Walking into the first codebase review was like opening a time capsule.\nAPL's concise syntax meant that complex business logic could be written in just a few lines.\nBeautiful, if you could read it.\nTerrifying, if you couldn't.\nAnd most of us couldn't.</p>\n<p>The original team was invaluable during the knowledge transfer.\nThey knew every quirk, every special case, every business rule that had been added over the decades.\nBut there's only so much you can learn from conversations.\nDocumentation was sparse.\nWhat existed was outdated.\nThe real documentation was in the heads of the original developers.</p>\n<p>We spent weeks mapping the system's functionality:</p>\n<ul>\n<li>The core manufacturing process was spread across 50+ tables with complex interdependencies</li>\n<li>Inventory management touched nearly every part of the system</li>\n<li>Custom reporting tools have been built over decades to meet specific business needs</li>\n<li>Integration points with external components were handled through a maze of stored procedures</li>\n</ul>\n<p>Tables that started with basic schemas had grown to include hundreds of columns.\nSome columns were no longer used but couldn't be removed because no one was sure if some obscure report still needed them.</p>\n<p>What made this particularly challenging was the disconnect between the business processes and their technical implementation.\nThe business would describe a simple workflow, but the technical implementation would reveal layers of complexity added over years of edge cases and special requirements.</p>\n<p>We needed a systematic approach to understanding this beast.\nWe started by mapping business processes and their corresponding technical implementations.\nThis helped us identify the core domains that would later influence our modular architecture.\nMore importantly, it helped us understand the true scope of what we were dealing with.</p>\n<h2>The Product vs. Engineering Conflict</h2>\n<p>Management wanted quick wins.\nThey pushed us to start with the simplest components.\nThis created tension between product management and the development team.</p>\n<p>Product management's perspective was straightforward: show progress to the business.\nThey needed visible results to justify the investment in the rewrite.\nThe business was spending significant money, and they wanted to see returns quickly.</p>\n<p>The development team saw a different reality.\nWe knew that starting with peripheral features meant building on shaky ground.\nThe core business logic would remain in the legacy system, making every integration point more complex.\nThis technical debt would compound over time.</p>\n<p>As a technical lead, I strongly opposed this approach.\nMy argument was simple: the core manufacturing process was the heart of the system.\nEvery peripheral feature depended on it.\nBy postponing its migration, we created a tangled web of dependencies between old and new systems.\nEach new feature we migrated would need complex synchronization with the legacy core.\nWe were building on quicksand.</p>\n<p>I advocated for focusing on the core domain first.\nYes, it would take longer to show the first results.\nBut it would create a solid foundation for everything that followed.\nThe business would have to wait longer for visible progress, but the overall migration would be faster and more reliable.</p>\n<p>Neither side was wrong in their objectives.\nProduct management had valid concerns about showing progress.\nThe development team had valid concerns about technical sustainability.\nBut this misalignment led to compromises that impacted the project timeline.\nTo this day, I believe we would have finished the migration sooner if we had started with the core business logic.</p>\n<h2>Software Architecture: Building for the Future</h2>\n<p>During the discovery phase, we identified distinct business domains within the system.\nThis led us to implement a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith architecture</strong></a>.\nEach module would be self-contained but able to communicate with others through a shared event bus:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_122/legacy_system_with_modular_monolith.png\" alt=\"Modular monolith architecture with legacy system and a message broker for communication.\">\n<p>Key architectural decisions:</p>\n<ol>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>Modular monolith</strong></a>:\nEach module represented a distinct business domain.\nThis provided a clear path to potential future microservices if needed.</p>\n</li>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>Asynchronous communication</strong></a>:\nModules communicated through events using RabbitMQ.\nThis reduced coupling and improved system resilience.</p>\n</li>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>Shared database with boundaries</strong></a>:\nWhile all modules used the same PostgreSQL database, each had its own set of tables and schemas.\nThis helped us maintain logical separation.</p>\n</li>\n<li>\n<p><a href=\"https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development\"><strong>Cloud-ready design</strong></a>:\nThe system was deployed to AWS using containerization.\nA Jenkins pipeline enabled deployments to multiple environments in minutes.</p>\n</li>\n</ol>\n<h2>The Data Sync Challenge</h2>\n<p>The two-way data synchronization was more complex than initially anticipated.\nHere's why we couldn't use existing <a href=\"https://en.wikipedia.org/wiki/Change_data_capture\">change data capture</a> (CDC) solutions like <a href=\"https://debezium.io/\">Debezium</a>:</p>\n<ol>\n<li>\n<p><strong>Complex transformations</strong>:\nMany legacy tables required data from multiple new tables.\nThis wasn't a simple one-to-one mapping that CDC tools excel at.</p>\n</li>\n<li>\n<p><strong>Business logic in sync</strong>:\nThe sync process needed to apply business rules during transformation.\nThis went beyond what most replication tools provide.</p>\n</li>\n<li>\n<p><strong>Bidirectional requirements</strong>:\nWe needed to sync both ways while preventing infinite loops.\nThe legacy system remained the source of truth for non-migrated components.</p>\n</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_122/data_sync.png\" alt=\"Data sync flow between modern and legacy system.\">\n<p>We built a custom solution using RabbitMQ for message transport.\nWhile this worked for us, the lesson remains: evaluate existing tools thoroughly before building custom solutions.\nEven if you can't use them entirely, you might learn valuable patterns from their approaches.</p>\n<h2>Key Technical Lessons</h2>\n<ol>\n<li>\n<p><strong>Modular architecture pays off</strong>:\nThe modular monolith approach made the system easier to understand and maintain.\nEach module had clear boundaries and responsibilities.</p>\n</li>\n<li>\n<p><strong>Invest in deployment automation</strong>:\nThe CI/CD pipeline was crucial.\nIt allowed us to deploy confidently and frequently, reducing the risk of each change.</p>\n</li>\n<li>\n<p><strong>Message-based integration</strong>:\nAsync communication between modules provided the flexibility needed for the gradual migration.</p>\n</li>\n<li>\n<p><strong>Data sync complexity</strong>:\nDon't underestimate the complexity of data synchronization in legacy migrations.\nWhether using existing tools or building custom solutions, this will be a major challenge.</p>\n</li>\n</ol>\n<h2>The Human Factor</h2>\n<p>Technical challenges are only part of the story.\nThe success of legacy rewrites depends heavily on managing different stakeholders:</p>\n<ol>\n<li>Product Management needs to see progress</li>\n<li>Development teams need time to do things right</li>\n<li>The business needs to keep running</li>\n<li>The legacy team needs to transfer knowledge</li>\n</ol>\n<p>Finding the right balance between these competing needs can be tricky.</p>\n<p>We found several approaches that helped:</p>\n<ul>\n<li>Regular stakeholder meetings where each group could voice concerns</li>\n<li>Transparent project tracking visible to all parties</li>\n<li>Clear communication about technical decisions and their business impact</li>\n<li>Celebration of both technical and business milestones</li>\n<li>Documentation of both technical and institutional knowledge</li>\n</ul>\n<p>I can't stress enough how important it was to document the knowledge acquired over four decades of operating the legacy system.\nWhen the original team retired, we had a comprehensive set of documents that explained every business rule and every edge case.</p>\n<h2>Results That Matter</h2>\n<p>Four years later, the system is thriving.\nThe cloud infrastructure provides reliability and scalability.\nThe <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>modular monolith architecture</strong></a> makes it maintainable.\nThe automated deployment pipeline enables rapid updates.</p>\n<p>But the journey taught us valuable lessons about balancing technical needs with business pressures.\nSuccess in legacy rewrites requires more than just technical excellence.\nIt requires understanding the business domain, managing stakeholder expectations, and making pragmatic architectural decisions.</p>\n<p>Software architecture matters, but so does the human factor. Plan for both.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/what-rewriting-a-40-year-old-project-taught-me-about-software-development",
            "title": "What Rewriting a 40-Year-Old Project Taught Me About Software Development",
            "summary": "I spent four years rewriting a 40-year-old manufacturing system written in APL while the $10M business it powered kept running.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_122.png",
            "date_modified": "2024-12-28T00:00:00.000Z",
            "date_published": "2024-12-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-in-dotnet-advanced-concepts",
            "content_html": "<p>Quartz.NET runs background jobs in ASP.NET Core through the <code>IJob</code> interface, triggered once at a given time or on a cron schedule.\nJobs live in memory by default and are lost on restart, so production setups call <code>UsePersistentStore</code> against a database.\n<code>AddQuartzInstrumentation</code> gives you a trace for every execution.</p>\n<p>Most ASP.NET Core applications need to handle <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\"><strong>background processing</strong></a> -\nfrom sending reminder emails to running cleanup tasks.\nWhile there are many ways to implement background jobs, <a href=\"https://www.quartz-scheduler.net/\">Quartz.NET</a>\nstands out with its robust scheduling capabilities, persistence options, and production-ready features.</p>\n<p>In this article, we'll look at:</p>\n<ul>\n<li>Setting up Quartz.NET with ASP.NET Core and proper observability</li>\n<li>Implementing both on-demand and recurring jobs</li>\n<li>Configuring persistent storage with PostgreSQL</li>\n<li>Handling job data and monitoring execution</li>\n</ul>\n<p>Let's start with the basic setup and build our way up to a production-ready configuration.</p>\n<h2>Setting Up Quartz With ASP.NET Core</h2>\n<p>First, let's set up Quartz with proper instrumentation.</p>\n<p>We'll need to install some NuGet packages:</p>\n<pre><code class=\"language-powershell\">Install-Package Quartz.Extensions.Hosting\nInstall-Package Quartz.Serialization.Json\n\n# This might be in prerelease\nInstall-Package OpenTelemetry.Instrumentation.Quartz\n</code></pre>\n<p>Next, we'll configure the Quartz services and <a href=\"https://milanjovanovic.tech/blog/opentelemetry-dotnet-guide\"><strong>OpenTelemetry instrumentation</strong></a> and start the scheduler:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddQuartz();\n\n// Add Quartz.NET as a hosted service\nbuilder.Services.AddQuartzHostedService(options =&gt;\n{\n    options.WaitForJobsToComplete = true;\n});\n\nbuilder.Services.AddOpenTelemetry()\n    .WithTracing(tracing =&gt;\n    {\n        tracing\n            .AddHttpClientInstrumentation()\n            .AddAspNetCoreInstrumentation()\n            .AddQuartzInstrumentation();\n    })\n    .UseOtlpExporter();\n</code></pre>\n<p>This is all we need at the start.</p>\n<h2>Defining and Scheduling Jobs</h2>\n<p>To define a <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>background job</strong></a>, you have to implement the <code>IJob</code> interface.\nAll job implementations run as scoped services, so you can inject dependencies as needed.\nQuartz allows you to pass data to a job using the <code>JobDataMap</code> dictionary.\nIt's recommended to only use primitive types for job data to avoid serialization issues.</p>\n<p>When executing the job, there are a few ways to fetch job data:</p>\n<ul>\n<li><code>JobDataMap</code> - a dictionary of key-value pairs\n<ul>\n<li><code>JobExecutionContext.JobDetail.JobDataMap</code> - job-specific data</li>\n<li><code>JobExecutionContext.Trigger.TriggerDataMap</code> - trigger-specific data</li>\n</ul>\n</li>\n<li><code>MergedJobDataMap</code> - combines job data with trigger data</li>\n</ul>\n<p>It's a best practice to use <code>MergedJobDataMap</code> to retrieve job data.</p>\n<pre><code class=\"language-csharp\">public class EmailReminderJob(ILogger&lt;EmailReminderJob&gt; logger, IEmailService emailService) : IJob\n{\n    public const string Name = nameof(EmailReminderJob);\n\n    public async Task Execute(IJobExecutionContext context)\n    {\n        // Best practice: Prefer using MergedJobDataMap\n        var data = context.MergedJobDataMap;\n\n        // Get job data - note that this isn't strongly typed\n        string? userId = data.GetString(&quot;userId&quot;);\n        string? message = data.GetString(&quot;message&quot;);\n\n        try\n        {\n            await emailService.SendReminderAsync(userId, message);\n\n            logger.LogInformation(&quot;Sent reminder to user {UserId}: {Message}&quot;, userId, message);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, &quot;Failed to send reminder to user {UserId}&quot;, userId);\n\n            // Rethrow to let Quartz handle retry logic\n            throw;\n        }\n    }\n}\n</code></pre>\n<p>One thing to note: <code>JobDataMap</code> isn't strongly typed. This is a limitation we have to live with, but we can mitigate it by:</p>\n<ol>\n<li>Using constants for key names</li>\n<li>Validating data early in the <code>Execute</code> method</li>\n<li>Creating wrapper services for job scheduling</li>\n</ol>\n<p>Now, let's discuss scheduling jobs.</p>\n<p>Here's how to schedule one-time reminders:</p>\n<pre><code class=\"language-csharp\">public record ScheduleReminderRequest(\n    string UserId,\n    string Message,\n    DateTime ScheduleTime\n);\n\n// Schedule a one-time reminder\napp.MapPost(&quot;/api/reminders/schedule&quot;, async (\n    ISchedulerFactory schedulerFactory,\n    ScheduleReminderRequest request) =&gt;\n{\n    var scheduler = await schedulerFactory.GetScheduler();\n\n    var jobData = new JobDataMap\n    {\n        { &quot;userId&quot;, request.UserId },\n        { &quot;message&quot;, request.Message }\n    };\n\n    var job = JobBuilder.Create&lt;EmailReminderJob&gt;()\n        .WithIdentity($&quot;reminder-{Guid.NewGuid()}&quot;, &quot;email-reminders&quot;)\n        .SetJobData(jobData)\n        .Build();\n\n    var trigger = TriggerBuilder.Create()\n        .WithIdentity($&quot;trigger-{Guid.NewGuid()}&quot;, &quot;email-reminders&quot;)\n        .StartAt(request.ScheduleTime)\n        .Build();\n\n    await scheduler.ScheduleJob(job, trigger);\n\n    return Results.Ok(new { scheduled = true, scheduledTime = request.ScheduleTime });\n})\n.WithName(&quot;ScheduleReminder&quot;)\n.WithOpenApi();\n\n</code></pre>\n<p>The endpoint schedules one-time email reminders using Quartz.\nIt creates a job with user data, sets up a trigger for the specified time, and schedules them together.\nThe <code>EmailReminderJob</code> receives a unique identity in the <code>email-reminders</code> group.</p>\n<p>Here's a sample request you can use to test this out:</p>\n<pre><code>POST /api/reminders/schedule\n{\n    &quot;userId&quot;: &quot;user123&quot;,\n    &quot;message&quot;: &quot;Important meeting!&quot;,\n    &quot;scheduleTime&quot;: &quot;2024-12-17T15:00:00&quot;\n}\n</code></pre>\n<h2>Scheduling Recurring Jobs</h2>\n<p>For recurring background jobs, you can use <a href=\"https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontriggers.html\">cron schedules</a>:</p>\n<pre><code class=\"language-csharp\">public record RecurringReminderRequest(\n    string UserId,\n    string Message,\n    string CronExpression\n);\n\n// Schedule a recurring reminder\napp.MapPost(&quot;/api/reminders/schedule/recurring&quot;, async (\n    ISchedulerFactory schedulerFactory,\n    RecurringReminderRequest request) =&gt;\n{\n    var scheduler = await schedulerFactory.GetScheduler();\n\n    var jobData = new JobDataMap\n    {\n        { &quot;userId&quot;, request.UserId },\n        { &quot;message&quot;, request.Message }\n    };\n\n    var job = JobBuilder.Create&lt;EmailReminderJob&gt;()\n        .WithIdentity($&quot;recurring-{Guid.NewGuid()}&quot;, &quot;recurring-reminders&quot;)\n        .SetJobData(jobData)\n        .Build();\n\n    var trigger = TriggerBuilder.Create()\n        .WithIdentity($&quot;recurring-trigger-{Guid.NewGuid()}&quot;, &quot;recurring-reminders&quot;)\n        .WithCronSchedule(request.CronExpression)\n        .Build();\n\n    await scheduler.ScheduleJob(job, trigger);\n\n    return Results.Ok(new { scheduled = true, cronExpression = request.CronExpression });\n})\n.WithName(&quot;ScheduleRecurringReminder&quot;)\n.WithOpenApi();\n</code></pre>\n<p>Cron triggers are more powerful than simple triggers.\nThey allow you to define complex schedules like &quot;every weekday at 10 AM&quot; or &quot;every 15 minutes&quot;.\nQuartz supports cron expressions with seconds, minutes, hours, days, months, and years.</p>\n<p>Here's a sample request if you want to test this:</p>\n<pre><code>POST /api/reminders/schedule/recurring\n{\n    &quot;userId&quot;: &quot;user123&quot;,\n    &quot;message&quot;: &quot;Daily standup&quot;,\n    &quot;cronExpression&quot;: &quot;0 0 10 ? * MON-FRI&quot;\n}\n</code></pre>\n<h2>Job Persistence Setup</h2>\n<p>By default, Quartz uses in-memory storage, which means your jobs are lost when the application restarts.\nFor production environments, you'll want to use a persistent store.\nQuartz supports several <a href=\"https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/job-stores.html\">database providers</a>,\nincluding SQL Server, PostgreSQL, MySQL, and Oracle.</p>\n<p>Let's look at how to set up persistent storage with proper schema isolation:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddQuartz(options =&gt;\n{\n    options.AddJob&lt;EmailReminderJob&gt;(c =&gt; c\n        .StoreDurably()\n        .WithIdentity(EmailReminderJob.Name));\n\n    options.UsePersistentStore(persistenceOptions =&gt;\n    {\n        persistenceOptions.UsePostgres(cfg =&gt;\n        {\n            cfg.ConnectionString = connectionString;\n            cfg.TablePrefix = &quot;scheduler.qrtz_&quot;;\n        },\n        dataSourceName: &quot;reminders&quot;); // Database name\n\n        persistenceOptions.UseNewtonsoftJsonSerializer();\n        persistenceOptions.UseProperties = true;\n    });\n});\n</code></pre>\n<p>A few important things to note here:</p>\n<ul>\n<li>The <code>TablePrefix</code> setting helps organize Quartz tables in your database - in this case, placing them in a dedicated <code>scheduler</code> schema</li>\n<li>You'll need to run the appropriate database scripts to create these tables</li>\n<li>Each database provider has its own <a href=\"https://github.com/quartznet/quartznet/tree/main/database/tables\">setup scripts</a> -\ncheck the Quartz documentation for your chosen provider</li>\n</ul>\n<h3>Durable Jobs</h3>\n<p>Notice how we're configuring the <code>EmailReminderJob</code> with <code>StoreDurably</code>?\nThis is a powerful pattern that lets you define your jobs once and reuse them with different triggers.\nHere's how to schedule a stored job:</p>\n<pre><code class=\"language-csharp\">public async Task ScheduleReminder(string userId, string message, DateTime scheduledTime)\n{\n    var scheduler = await _schedulerFactory.GetScheduler();\n\n    // Reference the stored job by its identity\n    var jobKey = new JobKey(EmailReminderJob.Name);\n\n    var trigger = TriggerBuilder.Create()\n        .ForJob(jobKey)  // Reference the durable job\n        .WithIdentity($&quot;trigger-{Guid.NewGuid()}&quot;)\n        .UsingJobData(&quot;userId&quot;, userId)\n        .UsingJobData(&quot;message&quot;, message)\n        .StartAt(scheduledTime)\n        .Build();\n\n    await scheduler.ScheduleJob(trigger);  // Note: just passing the trigger\n}\n</code></pre>\n<p>This approach has several benefits:</p>\n<ul>\n<li>Job definitions are centralized in your startup configuration</li>\n<li>You can't accidentally schedule a job that hasn't been properly configured</li>\n<li>Job configurations are consistent across all schedules</li>\n</ul>\n<h2>Summary</h2>\n<p>Getting <strong>Quartz</strong> set up properly in .NET involves more than just adding the NuGet package.</p>\n<p>Pay attention to:</p>\n<ol>\n<li>Proper job definition and data handling with <code>JobDataMap</code></li>\n<li>Setting up both one-time and recurring job schedules</li>\n<li>Configuring persistent storage with proper schema isolation</li>\n<li>Using durable jobs to maintain consistent job definitions</li>\n</ol>\n<p>Each of these elements contributes to a reliable background processing system that can grow with your application's needs.\nA good example of using background jobs is when you want to <a href=\"https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way\"><strong>build asynchronous APIs</strong></a>.</p>\n<p>Good luck out there, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-in-dotnet-advanced-concepts",
            "title": "Scheduling Background Jobs With Quartz in .NET (advanced concepts)",
            "summary": "Quartz.NET is a powerful job scheduling library, but integrating it properly with ASP.NET Core requires careful consideration.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_121.png",
            "date_modified": "2024-12-21T00:00:00.000Z",
            "date_published": "2024-12-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths",
            "content_html": "<p>A module's public API is the explicit contract other modules are allowed to call, while its domain model, services, and database schema stay internal.\nExpose only what another module actually needs, shaped around its use case: <code>GetOrderForShippingAsync</code> rather than a generic <code>GetOrderAsync</code>.\nQueries that span modules get their own read model, updated through events.</p>\n<p>Every article about modular monoliths tells you to use public APIs between modules.\nBut they rarely tell you why these APIs exist or how to design them properly.</p>\n<p>A modular monolith organizes an application into independent modules that have clear boundaries.\nThe <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>module boundaries</strong></a> are logical and group related business capabilities together.</p>\n<p>After building several large-scale modular monoliths, I've learned that public APIs are not just about clean code - they're about controlling chaos.\nLet me show you what I mean.</p>\n<h2>The Reality of Module Communication</h2>\n<p>Here's what nobody tells you about public APIs in modular monoliths: they represent intentional coupling points.\nYes, you read that right.\nPublic APIs don't eliminate coupling - they make it explicit and controllable.</p>\n<p>When Module A needs something from Module B, you have three options:</p>\n<ol>\n<li>Let Module A read directly from Module B's database</li>\n<li>Let Module A access Module B's internal services</li>\n<li>Create a public API that explicitly defines what Module A can do</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_120/module_communication_options.png\" alt=\"Module communication options: direct database access, calling internal services, calling public API.\">\n<p>The first two options lead to chaos.\nI've seen entire systems become unmaintainable because every module was freely accessing the data and services of other modules.</p>\n<p>The previous options are examples of synchronous communication between modules.\nBut you can also implement <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>asynchonrous module communication</strong></a> using messaging.\nWe have to adjust the technical implementation.\nHowever, modules still have a public API in message contracts.</p>\n<h2>Why We Need Public APIs</h2>\n<p>Public APIs serve three critical purposes:</p>\n<ol>\n<li><strong>Contract Definition</strong>: They explicitly state what other modules can and cannot do</li>\n<li><strong>Dependency Control</strong>: They force you to think about module dependencies</li>\n<li><strong>Change Management</strong>: They provide a stable interface while allowing internal changes</li>\n</ol>\n<p>Here's a practical example. Imagine you have an Orders module and a Shipping module.</p>\n<p>This is what you want to avoid:</p>\n<pre><code class=\"language-csharp\">public class ShippingService\n{\n    private readonly OrdersDbContext _ordersDb; // Direct database access\n\n    public async Task ShipOrder(string orderId)\n    {\n        // Directly reading from another module's database\n        var order = await _ordersDb.Orders\n            .Include(o =&gt; o.Lines)\n            .FirstOrDefaultAsync(o =&gt; o.Id == orderId);\n\n        // What happens if the Orders module changes its schema?\n        // What if it moves to a different database?\n    }\n}\n</code></pre>\n<p>This is what you want to achieve instead:</p>\n<pre><code class=\"language-csharp\">public class ShippingService\n{\n    private readonly IOrdersModule _orders; // Public API access\n\n    public async Task ShipOrder(string orderId)\n    {\n        // Using the public API\n        var order = await _orders.GetOrderForShippingAsync(orderId);\n\n        // The Orders module can change its internals\n        // as long as it maintains this contract\n    }\n}\n</code></pre>\n<h2>Controlling What Gets Exposed</h2>\n<p>The hardest part of designing public APIs is deciding what to expose.\nHere's my rule of thumb:</p>\n<ol>\n<li>Start with nothing public</li>\n<li>Expose only what other modules actually need</li>\n<li>Design the API around use cases, not data</li>\n</ol>\n<p>Here's how this looks in practice:</p>\n<pre><code class=\"language-csharp\">public interface IOrdersModule\n{\n    // Don't expose generic CRUD operations\n    // Task&lt;Order&gt; GetOrderAsync(string orderId); // Bad\n\n    // Instead, expose specific use cases\n    Task&lt;OrderShippingInfo&gt; GetOrderForShippingAsync(string orderId);\n    Task&lt;OrderPaymentInfo&gt; GetOrderForPaymentAsync(string orderId);\n    Task&lt;OrderSummary&gt; GetOrderForCustomerAsync(string orderId);\n}\n</code></pre>\n<h2>Protecting Your Module's Data</h2>\n<p>Public APIs aren't enough.\nYou also need to <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>protect your module's data</strong></a>.\nHere's what I've found works:</p>\n<ol>\n<li><strong>Separate Schemas</strong>: Each module gets <a href=\"https://milanjovanovic.tech/blog/schema-per-module-vs-database-per-module\"><strong>its own database schema</strong></a>.</li>\n</ol>\n<pre><code class=\"language-sql\">CREATE SCHEMA Orders;\nCREATE SCHEMA Shipping;\n\n-- Orders module can only access its schema\nCREATE USER OrdersUser WITH DEFAULT_SCHEMA = Orders;\nGRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::Orders TO OrdersUser;\n\n-- Shipping module can only access its schema\nCREATE USER ShippingUser WITH DEFAULT_SCHEMA = Shipping;\nGRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::Shipping TO ShippingUser;\n</code></pre>\n<p>We can also lock down the user's access to a given schema to only allow reading and writing data.</p>\n<ol start=\"2\">\n<li><strong>Different Connection Strings</strong>: Each module gets its own database user with a respective connection string.</li>\n</ol>\n<pre><code class=\"language-csharp\">builder.Services.AddDbContext&lt;OrdersDbContext&gt;(options =&gt;\n    options.UseSqlServer(builder.Configuration.GetConnectionString(&quot;OrdersConnection&quot;)));\n\nbuilder.Services.AddDbContext&lt;ShippingDbContext&gt;(options =&gt;\n    options.UseSqlServer(builder.Configuration.GetConnectionString(&quot;ShippingConnection&quot;)));\n</code></pre>\n<p>If you want to learn more about this, check out this article about <a href=\"https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application\"><strong>using multiple EF Core DbContexts</strong></a>.</p>\n<ol start=\"3\">\n<li><strong>Read Models</strong>: Create specific read models for other modules.</li>\n</ol>\n<pre><code class=\"language-csharp\">internal class Order\n{\n    // Internal domain model with full complexity\n}\n\npublic class OrderShippingInfo\n{\n    // Public DTO with only what shipping needs\n    public string OrderId { get; init; }\n    public Address ShippingAddress { get; init; }\n    public List&lt;ShippingItem&gt; Items { get; init; }\n}\n</code></pre>\n<h2>Dealing with Cross-Cutting Concerns</h2>\n<p>Some features naturally span multiple modules.\nFor example, when a customer views their order history, you might need data from the Orders, Shipping, and Payments modules.</p>\n<p>Don't try to force this through module APIs.\nInstead:</p>\n<ol>\n<li>Create a separate query model</li>\n<li>Use <a href=\"https://milanjovanovic.tech/blog/event-driven-communication-modules\"><strong>event-driven patterns</strong></a> to keep it updated</li>\n<li>Own it in a dedicated module or one of the existing modules</li>\n</ol>\n<pre><code class=\"language-csharp\">public class OrderHistoryModule\n{\n    public async Task&lt;CustomerOrderHistory&gt; GetOrderHistoryAsync(string customerId)\n    {\n        // Read from a dedicated read model that's kept\n        // updated through events from other modules\n        return await _orderHistoryRepository.GetCustomerHistoryAsync(customerId);\n    }\n}\n</code></pre>\n<h2>Summary</h2>\n<p>Public APIs in modular monoliths are not about preventing coupling - they're about controlling it.\nEvery public API is a contract that says: &quot;Yes, these modules are coupled, and this is exactly how they depend on each other.&quot;</p>\n<p>The goal isn't to eliminate dependencies between modules.\nThe goal is to make them explicit, controlled, and maintainable.</p>\n<p>Get this right, and your modular monolith will be easier to maintain, test, and evolve.\nGet it wrong, and you'll end up with a distributed big ball of mud.</p>\n<p>Want to master building modular monoliths with clean APIs and event-driven patterns?\nCheck out my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course, where I'll show you how to build maintainable systems\nusing practical examples from real projects.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths",
            "title": "Internal vs. Public APIs in Modular Monoliths",
            "summary": "Every article about modular monoliths tells you to use public APIs between modules, but few explain why they exist or how to design them.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_120.png",
            "date_modified": "2024-12-14T00:00:00.000Z",
            "date_published": "2024-12-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/central-package-management-in-net-simplify-nuget-dependencies",
            "content_html": "<p>Central Package Management lets you define every NuGet version once, in a <code>Directory.Packages.props</code> file at the solution root.\nProjects then reference packages without a <code>Version</code> attribute, so all of them stay on the same version.\nIt needs NuGet 6.2 and .NET SDK 6.0.300 or newer.</p>\n<p>I remember the days when managing NuGet packages across multiple projects was a real pain.\nYou know what I mean - you open a large solution and find out every project uses a different version of the same package.\nNot fun!</p>\n<p>Let me show you how <strong>Central Package Management</strong> (CPM) in .NET can fix this problem once and for all.</p>\n<h2>The Problem We Need to Solve</h2>\n<p>I often work with solutions that have lots of projects.\nIt's not uncommon to have solutions with 30 or more projects.\nEach one needs similar packages like Serilog or Polly.\nMost test projects I create depend on xUnit.\nBefore CPM, keeping track of package versions was a mess:</p>\n<ul>\n<li>One project uses Serilog <code>4.1.0</code></li>\n<li>Another uses Serilog <code>4.0.2</code></li>\n<li>And somehow, a third one uses Serilog <code>3.1.1</code></li>\n</ul>\n<p>This causes real problems.\nDifferent versions can behave differently, leading to weird bugs that are hard to track down.\nI've wasted many hours fixing issues caused by version mismatches.</p>\n<h2>How Central Package Management Helps</h2>\n<p>Think of CPM as a control center for all your package versions.\nInstead of setting versions in each project, you set them once in one place.\nThen, you just reference a package you want to use without specifying the version.\nIt's that simple.</p>\n<p>Here's what you need to use <a href=\"https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management\">Central Package Management</a>:</p>\n<ul>\n<li>NuGet 6.2 or newer</li>\n<li>.NET SDK 6.0.300 or newer</li>\n<li>If you use Visual Studio, you need version 2022 17.2 or newer</li>\n</ul>\n<h2>Setting It Up</h2>\n<p>Let me show you how to set up CPM.\nIt's easier than you might think.</p>\n<ol>\n<li>First, create a file called <code>Directory.Packages.props</code> in your solution's main folder:</li>\n</ol>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n  &lt;PropertyGroup&gt;\n    &lt;ManagePackageVersionsCentrally&gt;true&lt;/ManagePackageVersionsCentrally&gt;\n  &lt;/PropertyGroup&gt;\n  &lt;ItemGroup&gt;\n    &lt;PackageVersion Include=&quot;Newtonsoft.Json&quot; Version=&quot;13.0.3&quot; /&gt;\n    &lt;PackageVersion Include=&quot;Serilog&quot; Version=&quot;4.1.0&quot; /&gt;\n    &lt;PackageVersion Include=&quot;Polly&quot; Version=&quot;8.5.0&quot; /&gt;\n  &lt;/ItemGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>Note the use of <code>PackageVersion</code> to define NuGet dependencies.</p>\n<ol start=\"2\">\n<li>In your project files, you can list the packages using <code>PackageReference</code> without the version component:</li>\n</ol>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;PackageReference Include=&quot;Newtonsoft.Json&quot; /&gt;\n  &lt;PackageReference Include=&quot;AutoMapper&quot; /&gt;\n  &lt;PackageReference Include=&quot;Polly&quot; /&gt;\n&lt;/ItemGroup&gt;\n</code></pre>\n<p>That's it!\nNow all your projects will use the same package versions.</p>\n<h2>Cool Things You Can Do</h2>\n<h3>Need a Different Version for One Project?</h3>\n<p>Sometimes you might need a specific project to use a different version.\nNo problem!\nJust add this to your project file:</p>\n<pre><code class=\"language-xml\">&lt;PackageReference Include=&quot;Serilog&quot; VersionOverride=&quot;3.1.1&quot; /&gt;\n</code></pre>\n<p>The <code>VersionOverride</code> property lets you define the specific version you want to use.</p>\n<h3>Want a Package in Every Project?</h3>\n<p>If you have packages that every project needs, you can make them global.\nDefine a <code>GlobalPackageReference</code> in your props file:</p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;GlobalPackageReference Include=&quot;SonarAnalyzer.CSharp&quot; Version=&quot;10.3.0.106239&quot; /&gt;\n&lt;/ItemGroup&gt;\n</code></pre>\n<p>Now every project gets this package automatically!</p>\n<h2>Migrating Existing Projects to Central Package Management</h2>\n<ol>\n<li>Create the <code>Directory.Packages.props</code> file at the solution root</li>\n<li>Move all package versions from your <code>.csproj</code> files</li>\n<li>Remove version attributes from <code>PackageReference</code> elements</li>\n<li>Build your solution and fix any version conflicts</li>\n<li>Test thoroughly before committing</li>\n</ol>\n<p>Here's a Powershell script that will list all NuGet package versions in your solution:</p>\n<pre><code class=\"language-powershell\"># Scan all .csproj files and aggregate unique package versions\n$packages = Get-ChildItem -Filter *.csproj -Recurse |\n    Get-Content |\n    Select-String -Pattern '&lt;PackageReference Include=&quot;([^&quot;]+)&quot; Version=&quot;([^&quot;]+)&quot;' -AllMatches |\n    ForEach-Object { $_.Matches } |\n    Group-Object { $_.Groups[1].Value } |\n    ForEach-Object { @{\n        Name = $_.Name\n        Versions = $_.Group.ForEach({ $_.Groups[2].Value }) | Select-Object -Unique\n    }} |\n    Sort-Object { $_.Name }\n\n# Display results\n$packages | ForEach-Object {\n    &quot;$($_.Name) versions:&quot;\n    $_.Versions | ForEach-Object { &quot;  $_&quot; }\n}\n</code></pre>\n<p>There's also a CLI tool called <a href=\"https://github.com/Webreaper/CentralisedPackageConverter\">CentralisedPackageConverter</a>,\nwhich you can use to automate the migration.\nIt will scan for all .NET project files within that folder tree, gather all the versioned references in the projects,\nremove the versions from the project files, and write the entries to the <code>Directory.Packages.props</code> file.</p>\n<pre><code class=\"language-bash\"># Install the tool globally\ndotnet tool install CentralisedPackageConverter --global\n\n# Convert your solution to use Central Package Management\ncentral-pkg-converter /PATH_TO_YOUR_SOLUTION_FOLDER\n</code></pre>\n<h2>When Should You Use CPM?</h2>\n<p>I don't see a compelling reason for not using this by default.</p>\n<p>I recommend using CPM when:</p>\n<ul>\n<li>You have many projects that share packages</li>\n<li>You're tired of fixing version-related bugs</li>\n<li>You want to make sure everyone uses the same versions</li>\n</ul>\n<p>I recently added CPM to a solution with 30 projects.</p>\n<p>Here's what happened:</p>\n<ul>\n<li>Fewer merge conflicts</li>\n<li>Caught version problems early</li>\n<li>Made it easier for new team members</li>\n</ul>\n<p>This was especially helpful while migrating from .NET 8 to .NET 9.</p>\n<p>You can combine CPM with <a href=\"https://milanjovanovic.tech/blog/improving-code-quality-in-csharp-with-static-code-analysis\"><strong>build configuration and static code analysis</strong></a>.</p>\n<h2>Wrapping Up</h2>\n<p>My tips for success with <strong>Central Package Management</strong>:</p>\n<ol>\n<li>When you add CPM to an existing solution, do it in its own change/PR</li>\n<li>If you override a version, add a comment explaining why</li>\n<li>Check your package versions regularly for updates</li>\n<li>Only make packages global if you really need them everywhere</li>\n</ol>\n<p>Since I started using Central Package Management, managing NuGet packages has become much easier.\nIt's like having a single source of truth for all your package versions.</p>\n<p>Hope this was helpful.\nSee you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/central-package-management-in-net-simplify-nuget-dependencies",
            "title": "Central Package Management in .NET - Simplify NuGet Dependencies",
            "summary": "You open a large solution and find that every project uses a different version of the same package.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_119.png",
            "date_modified": "2024-12-07T00:00:00.000Z",
            "date_published": "2024-12-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit",
            "content_html": "<p>A saga breaks a distributed transaction into a sequence of local steps, each with a compensating action that undoes it when a later step fails.\nMassTransit implements this as a state machine: you define states, correlated events, and transitions in a <code>MassTransitStateMachine</code>, and persist the instance with the EF Core saga repository.</p>\n<p>Long-running business processes often involve multiple services working together.\nThink about an e-commerce order: you need to process the payment, update inventory, and notify shipping.\nTraditional distributed transactions using two-phase commit (2PC) seem like a solution, but they come with significant drawbacks.</p>\n<p>The main issue?\nServices can't make assumptions about how other services operate or how long they'll take.\nWhat if the payment service needs manual approval?\nWhat if the inventory check is delayed?\nHolding database locks across multiple services for extended periods isn't practical and can lead to system-wide issues.</p>\n<p>Let's look at how the Saga pattern solves these problems and implement it using MassTransit.</p>\n<h2>Understanding the Saga Pattern</h2>\n<p>A Saga is a sequence of related local transactions where each step has a defined action and a compensating action if something goes wrong.\nInstead of one big atomic transaction, we break the process into manageable steps that can be coordinated.</p>\n<p>Here's a simple order processing flow:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_118/order_flow.png\" alt=\"Sequence diagram showing an order processing flow with multiple services.\">\n</div>\n<p>Each step is independent and can be compensated if needed.\nIf the inventory service reports items are out of stock, we can refund the payment.\nThis approach gives us flexibility and reliability without tight coupling.</p>\n<h2>Implementing Sagas with MassTransit</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>MassTransit</strong></a> provides a state machine-based approach to implementing Sagas through its integration with Automatonymous.\nUnderstanding how state machines work is crucial for implementing effective sagas.</p>\n<h3>State Machine Fundamentals</h3>\n<p>A <a href=\"https://masstransit.io/documentation/patterns/saga/state-machine\">state machine</a> consists of several key components:</p>\n<ol>\n<li><strong>States</strong>: Represent the possible conditions of your saga instance</li>\n<li><strong>Events</strong>: Messages that can trigger state transitions</li>\n<li><strong>Behaviors</strong>: Actions that occur when events are received in specific states</li>\n<li><strong>Instance</strong>: Contains the data and current state for a specific saga</li>\n</ol>\n<p>Here's the state machine diagram for our order processing saga:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_118/order_saga_state_machine.png\" alt=\"State diagram showing an order saga state machine.\">\n</div>\n<p>Every state machine automatically includes <code>Initial</code> and <code>Final</code> states.\nThe <code>Initial</code> state is where new saga instances begin, and the <code>Final</code> state marks the end of a saga's lifecycle.</p>\n<h3>Defining the Saga Instance</h3>\n<p>The saga instance holds the data for a specific process:</p>\n<pre><code class=\"language-csharp\">public class OrderState : SagaStateMachineInstance\n{\n    public Guid CorrelationId { get; set; }\n    public string CurrentState { get; set; }\n\n    // Business data\n    public decimal OrderTotal { get; set; }\n    public string? PaymentIntentId { get; set; }\n    public DateTime? OrderDate { get; set; }\n    public string? CustomerEmail { get; set; }\n}\n</code></pre>\n<p>The <code>CorrelationId</code> uniquely identifies the saga instance, while <code>CurrentState</code> tracks its current state.\nAny additional properties store business data needed for the process.</p>\n<h3>Defining Events</h3>\n<p>Events are messages that can trigger state transitions. They must be correlated to a specific saga instance:</p>\n<pre><code class=\"language-csharp\">public record OrderSubmitted\n{\n    public Guid OrderId { get; init; }\n    public decimal Total { get; init; }\n    public string Email { get; init; }\n}\n\npublic record PaymentProcessed\n{\n    public Guid OrderId { get; init; }\n    public string PaymentIntentId { get; init; }\n}\n\npublic record InventoryReserved\n{\n    public Guid OrderId { get; init; }\n}\n\npublic record OrderFailed\n{\n    public Guid OrderId { get; init; }\n    public string Reason { get; init; }\n}\n</code></pre>\n<h3>Building the State Machine</h3>\n<p>Let's implement the order processing flow as a state machine:</p>\n<pre><code class=\"language-csharp\">public class OrderStateMachine : MassTransitStateMachine&lt;OrderState&gt;\n{\n    public OrderStateMachine()\n    {\n        Event(() =&gt; OrderSubmitted, x =&gt; x.CorrelateById(m =&gt; m.Message.OrderId));\n        Event(() =&gt; PaymentProcessed, x =&gt; x.CorrelateById(m =&gt; m.Message.OrderId));\n        Event(() =&gt; InventoryReserved, x =&gt; x.CorrelateById(m =&gt; m.Message.OrderId));\n        Event(() =&gt; OrderFailed, x =&gt; x.CorrelateById(m =&gt; m.Message.OrderId));\n\n        InstanceState(x =&gt; x.CurrentState);\n\n        Initially(\n            When(OrderSubmitted)\n                .Then(context =&gt;\n                {\n                    context.Saga.OrderTotal = context.Message.Total;\n                    context.Saga.CustomerEmail = context.Message.Email;\n                    context.Saga.OrderDate = DateTime.UtcNow;\n                })\n                .PublishAsync(context =&gt; context.Init&lt;ProcessPayment&gt;(new\n                {\n                    OrderId = context.Saga.CorrelationId,\n                    Amount = context.Saga.OrderTotal\n                }))\n                .TransitionTo(ProcessingPayment)\n        );\n\n        During(ProcessingPayment,\n            When(PaymentProcessed)\n                .PublishAsync(context =&gt; context.Init&lt;ReserveInventory&gt;(new\n                {\n                    OrderId = context.Saga.CorrelationId\n                }))\n                .TransitionTo(ReservingInventory),\n            When(OrderFailed)\n                .TransitionTo(Failed)\n                .Finalize()\n        );\n\n        During(ReservingInventory,\n            When(InventoryReserved)\n                .PublishAsync(context =&gt; context.Init&lt;OrderConfirmed&gt;(new\n                {\n                    OrderId = context.Saga.CorrelationId\n                }))\n                .TransitionTo(Completed)\n                .Finalize(),\n            When(OrderFailed)\n                .PublishAsync(context =&gt; context.Init&lt;RefundPayment&gt;(new\n                {\n                    OrderId = context.Saga.CorrelationId,\n                    Amount = context.Saga.OrderTotal\n                }))\n                .TransitionTo(Failed)\n                .Finalize()\n        );\n\n        SetCompletedWhenFinalized();\n    }\n\n    public State ProcessingPayment { get; private set; }\n    public State ReservingInventory { get; private set; }\n    public State Completed { get; private set; }\n    public State Failed { get; private set; }\n\n    public Event&lt;OrderSubmitted&gt; OrderSubmitted { get; private set; }\n    public Event&lt;PaymentProcessed&gt; PaymentProcessed { get; private set; }\n    public Event&lt;InventoryReserved&gt; InventoryReserved { get; private set; }\n    public Event&lt;OrderFailed&gt; OrderFailed { get; private set; }\n}\n</code></pre>\n<p>The state machine defines the possible states and transitions.\nEach step can trigger compensating actions if needed.\nFor example, if inventory reservation fails, we automatically trigger a payment refund.</p>\n<h3>Implementing Message Consumers</h3>\n<p>Services interact with the saga by consuming and publishing messages.\nHere's an example of a payment processing consumer:</p>\n<pre><code class=\"language-csharp\">public class ProcessPaymentConsumer(\n    IPaymentService paymentService,\n    ILogger&lt;ProcessPaymentConsumer&gt; logger) : IConsumer&lt;ProcessPayment&gt;\n{\n    public async Task Consume(ConsumeContext&lt;ProcessPayment&gt; context)\n    {\n        try\n        {\n            var paymentResult = await paymentService.ProcessPaymentAsync(\n                context.Message.OrderId,\n                context.Message.Amount\n            );\n\n            if (paymentResult.Succeeded)\n            {\n                await context.Publish&lt;PaymentProcessed&gt;(new\n                {\n                    OrderId = context.Message.OrderId,\n                    PaymentIntentId = paymentResult.PaymentIntentId\n                });\n            }\n            else\n            {\n                await context.Publish&lt;OrderFailed&gt;(new\n                {\n                    OrderId = context.Message.OrderId,\n                    Reason = paymentResult.FailureReason\n                });\n            }\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(\n                ex,\n                &quot;Failed to process payment for order {OrderId}&quot;,\n                context.Message.OrderId);\n\n            await context.Publish&lt;OrderFailed&gt;(new\n            {\n                OrderId = context.Message.OrderId,\n                Reason = &quot;Payment processing error&quot;\n            });\n        }\n    }\n}\n</code></pre>\n<p>Each consumer handles a specific part of the business process and communicates back to the saga through events.\nThis separation of concerns allows each service to focus on its specific responsibility while the saga coordinates the overall process.</p>\n<h2>Configuring MassTransit with PostgreSQL Persistence</h2>\n<p>To persist the saga state, we'll use PostgreSQL.\nFirst, let's install the required packages:</p>\n<pre><code class=\"language-powershell\">Install-Package MassTransit.EntityFrameworkCore\nInstall-Package Npgsql.EntityFrameworkCore.PostgreSQL\n</code></pre>\n<p>Create a <code>DbContext</code> for saga persistence:</p>\n<pre><code class=\"language-csharp\">public class OrderSagaDbContext : SagaDbContext\n{\n    public OrderSagaDbContext(DbContextOptions options) : base(options)\n    {\n    }\n\n    protected override IEnumerable&lt;ISagaClassMap&gt; Configurations\n    {\n        get\n        {\n            yield return new OrderStateMap();\n        }\n    }\n}\n\npublic class OrderStateMap : SagaClassMap&lt;OrderState&gt;\n{\n    protected override void Configure(EntityTypeBuilder&lt;OrderState&gt; entity, ModelBuilder model)\n    {\n        entity.Property(x =&gt; x.CurrentState).HasMaxLength(64);\n        entity.Property(x =&gt; x.CustomerEmail).HasMaxLength(256);\n        entity.Property(x =&gt; x.PaymentIntentId).HasMaxLength(64);\n    }\n}\n</code></pre>\n<p>Configure MassTransit in your application:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddDbContext&lt;OrderSagaDbContext&gt;(options =&gt;\n    options.UseNpgsql(builder.Configuration.GetConnectionString(&quot;Postgres&quot;)));\n\nbuilder.Services.AddMassTransit(x =&gt;\n{\n    x.AddSagaStateMachine&lt;OrderStateMachine, OrderState&gt;()\n        .EntityFrameworkRepository(r =&gt;\n        {\n            r.ConcurrencyMode = ConcurrencyMode.Pessimistic;\n            r.AddDbContext&lt;DbContext, OrderSagaDbContext&gt;();\n            r.UsePostgres();\n        });\n\n    x.UsingRabbitMq((context, cfg) =&gt;\n    {\n        cfg.Host(builder.Configuration.GetConnectionString(&quot;RabbitMQ&quot;));\n        cfg.ConfigureEndpoints(context);\n    });\n});\n</code></pre>\n<h2>Benefits of This Approach</h2>\n<p>Using sagas with MassTransit provides several advantages:</p>\n<ol>\n<li><strong>Fault Tolerance</strong>: Each step can be retried independently.\nAnd we can compensate failed operation, making our system more resilient.</li>\n<li><strong>State Visibility</strong>: The saga's state machine provides clear insight into where each process stands,\nmaking debugging and monitoring straightforward.</li>\n<li><strong>Loose Coupling</strong>: Services communicate through messages, allowing them to evolve independently while maintaining process integrity.</li>\n<li><strong>Maintainability</strong>: Changes can be made to individual steps without affecting others.</li>\n</ol>\n<p>The state machine approach also makes the business process explicit.\nEach state and transition is clearly defined, making it easier to understand and maintain the workflow.</p>\n<h2>Takeaway</h2>\n<p>The Saga pattern with MassTransit provides a robust solution for managing distributed business processes.\nInstead of dealing with distributed transactions, you get clear state management, automatic compensation for failures,\nand the ability to handle long-running operations without blocking resources.</p>\n<p>Want to dive deeper into event-driven architecture and distributed systems?\nCheck out my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course,\nwhere we explore sagas, event-driven patterns, and other essential techniques for building maintainable systems.</p>\n<p>Good luck out there, and see you next week.</p>\n<p><strong>P.S.</strong> You can find the code for the saga pattern implementation with MassTransit in <a href=\"https://github.com/m-jovanovic/saga-pattern-masstransit\">this repository</a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-masstransit",
            "title": "Implementing the Saga Pattern With MassTransit",
            "summary": "Long-running business processes span multiple services, and two-phase commit holds database locks across all of them.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_118.png",
            "date_modified": "2024-11-30T00:00:00.000Z",
            "date_published": "2024-11-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way",
            "content_html": "<p>An async API accepts the request, returns a tracking ID immediately, and does the heavy work in the background.\nThe client gets <code>202 Accepted</code> with a status URL in the <code>Location</code> header, then checks progress by ID.\nThis keeps long-running work like image processing off the HTTP request.</p>\n<p>Most APIs follow a simple pattern.\nThe client sends a request.\nThe server does some work.\nThe server sends back a response.</p>\n<p>This works well for fast operations like fetching data or simple updates.\nBut what about operations that take longer?</p>\n<p>Think about processing large files, generating reports, or converting videos.\nThese operations can take minutes or even hours.</p>\n<p>Making clients wait for these operations causes problems.</p>\n<h2>Understanding Async APIs</h2>\n<p>The key to handling long-running operations is to change how we think about API responses.\nAn async API splits work into two parts:</p>\n<ul>\n<li>Accept the request</li>\n<li>Process it later</li>\n</ul>\n<p>First, we accept the request and return a tracking ID immediately.\nThis gives users a quick response.\nThen, we process the actual work in the background, which won't block other requests.\nUsers can check the status of their request using the tracking ID whenever they want.</p>\n<p>This is different from <code>async</code>/<code>await</code> in C#.\nThat's about handling many requests at once (concurrently).\nThis is about handling long-running tasks better.\nWe're not just making the code asynchronous - we're making the entire operation asynchronous from the user's perspective.</p>\n<h2>The Problem with Sync APIs</h2>\n<p>Let's see this in practice with image processing. A typical image upload API might look like this:</p>\n<pre><code class=\"language-csharp\">[HttpPost]\npublic async Task&lt;IActionResult&gt; UploadImage(IFormFile file)\n{\n    if (file is null)\n    {\n        return BadRequest();\n    }\n\n    // Save original image\n    var originalPath = await SaveOriginalAsync(file);\n\n    // Generate thumbnails\n    var thumbnails = await GenerateThumbnailsAsync(originalPath);\n\n    // Optimize all images\n    await OptimizeImagesAsync(originalPath, thumbnails);\n\n    return Ok(new { originalPath, thumbnails });\n}\n</code></pre>\n<p>The client must wait while we save the file, generate thumbnails, and optimize images.\nOn a slow connection or with a large file, this request could time out.\nThe server is also stuck processing one image at a time.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_117/sync_api_request.png\" alt=\"Sequence diagram showing a synchronous API request.\">\n<h2>A Better Way: Async Processing</h2>\n<p>Let's fix these problems. We'll split the work into two parts:</p>\n<ol>\n<li>Accept the upload and return quickly</li>\n<li>Do the heavy work in the background</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_117/async_api_request.png\" alt=\"Sequence diagram showing an asynchronous API request.\">\n<h3>Uploading Images</h3>\n<p>Here's the new upload endpoint:</p>\n<pre><code class=\"language-csharp\">[HttpPost]\npublic async Task&lt;IActionResult&gt; UploadImage(IFormFile? file)\n{\n    if (file is null)\n    {\n        return BadRequest(&quot;No file uploaded.&quot;);\n    }\n\n    if (!imageService.IsValidImage(file))\n    {\n        return BadRequest(&quot;Invalid image file.&quot;);\n    }\n\n    // Phase 1: Accept the work\n    var id = Guid.NewGuid().ToString();\n    var folderPath = Path.Combine(_uploadDirectory, &quot;images&quot;, id);\n    var fileName = $&quot;{id}{Path.GetExtension(file.FileName)}&quot;;\n    var originalPath = await imageService.SaveOriginalImageAsync(\n        file,\n        folderPath,\n        fileName\n    );\n\n    // Queue Phase 2 for background processing\n    var job = new ImageProcessingJob(id, originalPath, folderPath);\n    await jobQueue.EnqueueAsync(job);\n\n    // Return status URL immediately\n    var statusUrl = GetStatusUrl(id);\n    return Accepted(statusUrl, new { id, status = &quot;queued&quot; });\n}\n</code></pre>\n<p>This new version only saves the original file during the HTTP request.\nThe heavy work moves to a background process.\nThe client immediately gets a status URL in the <code>Location</code> header instead of waiting.</p>\n<h3>Checking Progress</h3>\n<p>Clients can check their image's status using a separate endpoint:</p>\n<pre><code class=\"language-csharp\">[HttpGet(&quot;{id}/status&quot;)]\npublic IActionResult GetStatus(string id)\n{\n    if (!statusTracker.TryGetStatus(id, out var status))\n    {\n        return NotFound();\n    }\n\n    var response = new\n    {\n        id,\n        status,\n        links = new Dictionary&lt;string, string&gt;()\n    };\n\n    if (status == &quot;completed&quot;)\n    {\n        response.links = new Dictionary&lt;string, string&gt;\n        {\n            [&quot;original&quot;] = GetImageUrl(id),\n            [&quot;thumbnail&quot;] = GetThumbnailUrl(id, width: 200),\n            [&quot;preview&quot;] = GetThumbnailUrl(id, width: 800)\n        };\n    }\n\n    return Ok(response);\n}\n</code></pre>\n<h3>Processing Images in Background</h3>\n<p>The real work happens in the background processor.\nWhile the API handles new requests, a separate process works through the queued jobs.\nThis separation gives us flexibility in how we handle the processing.</p>\n<p>For single-server deployments, we can use .NET's <a href=\"https://milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels\"><strong>Channel</strong></a> type to queue jobs in memory:</p>\n<pre><code class=\"language-csharp\">public class JobQueue\n{\n    private readonly Channel&lt;ImageProcessingJob&gt; _channel;\n\n    public JobQueue()\n    {\n        var options = new BoundedChannelOptions(1000)\n        {\n            FullMode = BoundedChannelFullMode.Wait\n        };\n        _channel = Channel.CreateBounded&lt;ImageProcessingJob&gt;(options);\n    }\n\n    public async ValueTask EnqueueAsync(ImageProcessingJob job,\n        CancellationToken ct = default)\n    {\n        await _channel.Writer.WriteAsync(job, ct);\n    }\n\n    public IAsyncEnumerable&lt;ImageProcessingJob&gt; DequeueAsync(\n        CancellationToken ct = default)\n    {\n        return _channel.Reader.ReadAllAsync(ct);\n    }\n}\n</code></pre>\n<p>For multi-server setups, we need a distributed queue like RabbitMQ or even Redis.</p>\n<p>The background processor handles the time-consuming work:</p>\n<pre><code class=\"language-csharp\">public class ImageProcessor : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken ct)\n    {\n        await foreach (var job in jobQueue.DequeueAsync(ct))\n        {\n            try\n            {\n                await statusTracker.SetStatusAsync(\n                    job.Id,\n                    &quot;processing&quot;\n                );\n\n                // Generate thumbnails\n                await GenerateThumbnailsAsync(\n                    job.OriginalPath,\n                    job.OutputPath\n                );\n\n                // Optimize images\n                await OptimizeImagesAsync(\n                    job.OriginalPath,\n                    job.OutputPath\n                );\n\n                await statusTracker.SetStatusAsync(\n                    job.Id,\n                    &quot;completed&quot;\n                );\n            }\n            catch (Exception ex)\n            {\n                await statusTracker.SetStatusAsync(\n                    job.Id,\n                    &quot;failed&quot;\n                );\n\n                logger.LogError(ex, &quot;Failed to process image {Id}&quot;, job.Id);\n            }\n        }\n    }\n}\n</code></pre>\n<p>The background processor needs to handle failures gracefully.\nWe can improve <a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\"><strong>resilience</strong></a> by adding a retry policy with <a href=\"https://milanjovanovic.tech/blog/polly-v8-resilience-pipelines\"><strong>Polly</strong></a>.\nStatus updates keep users informed throughout the process.\nInstead of just &quot;processing&quot;, we tell them exactly what's happening.\nThis improves the user experience and helps with debugging.</p>\n<h2>Beyond Polling: Real-Time Updates</h2>\n<p>Our status endpoint works, but it puts the burden on clients.\nThey must repeatedly check for updates, leading to unnecessary server load.\nA client polling every second creates 60 requests per minute, yet most of these requests return the same status.</p>\n<p>We can flip this model around.\nInstead of clients asking for updates, the server can push updates when they happen.\nThis creates a more efficient and responsive system.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_117/async_api_request_with_push.png\" alt=\"Sequence diagram showing an asynchronous API request with server push for status updates.\">\n<p><a href=\"https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr\"><strong>SignalR and WebSockets</strong></a> enable real-time communication between server and client.\nWhen a job's status changes, the server immediately notifies interested clients.\nThis approach reduces network traffic and gives users instant feedback.</p>\n<p>For longer-running jobs, email notifications make more sense.\nUsers don't need to keep their browsers open.\nThey can close the tab and come back when notified.\nThis works well for reports that take hours to generate or batch processes that run overnight.</p>\n<p>Webhooks offer another option, especially for system-to-system communication.\nWhen a job completes, your server can notify other systems.\nThis enables workflow automation and system integration without constant polling.</p>\n<h2>Summary</h2>\n<p>Processing tasks asynchronously creates better experiences for everyone.\nUsers get immediate responses instead of watching spinning loading indicators.\nThey can start other tasks while waiting, and they'll know if something goes wrong.</p>\n<p>The benefits extend beyond user experience.\nServers can handle more requests because they're not tied up with long-running tasks.\n<a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>Background processors</strong></a> can retry failed operations without affecting the main application.\nYou can even scale your processing separately from your web servers.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\"><strong>Error handling</strong></a> improves too.\nWhen a long operation fails halfway through, you can save the progress and try again.\nUsers know exactly what's happening because they can check the status.\nThe system stays stable because one slow operation can't bring down your entire API.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-async-apis-in-aspnetcore-the-right-way",
            "title": "Building Async APIs in ASP.NET Core - The Right Way",
            "summary": "Not every API request needs to finish right away. Learn how to build better APIs by moving long-running tasks to the background.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_117.png",
            "date_modified": "2024-11-23T00:00:00.000Z",
            "date_published": "2024-11-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/hybrid-cache-in-aspnetcore-new-caching-library",
            "content_html": "<p><code>HybridCache</code> is a caching library introduced in .NET 9 that combines a fast in-memory L1 cache with a distributed L2 cache like Redis or SQL Server.\nIt protects against cache stampede and supports tag-based invalidation.\nThis issue covers the setup, the core methods, and adding Redis as the L2 cache.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>Caching</strong></a> is essential for building fast, scalable applications.\nASP.NET Core has traditionally offered two caching options: in-memory caching and distributed caching.\nEach has its trade-offs.\nIn-memory caching using <code>IMemoryCache</code> is fast but limited to a single server.\nDistributed caching with <code>IDistributedCache</code> works across multiple servers using a backplane.</p>\n<p>.NET 9 introduces <code>HybridCache</code>, a new library that combines the best of both approaches.\nIt prevents common caching problems like cache stampede.\nIt also adds useful features like tag-based invalidation and better performance monitoring.</p>\n<p>In this week's issue, I'll show you how to use <code>HybridCache</code> in your applications.</p>\n<h2>What is HybridCache?</h2>\n<p>The traditional caching options in ASP.NET Core have limitations.\nIn-memory caching is fast but limited to one server.\nDistributed caching works across servers but is slower.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid\">HybridCache</a> combines both approaches and adds important features:</p>\n<ul>\n<li>Two-level caching (L1/L2)\n<ul>\n<li>L1: Fast in-memory cache</li>\n<li>L2: Distributed cache (Redis, SQL Server, etc.)</li>\n</ul>\n</li>\n<li>Protection against <a href=\"https://en.wikipedia.org/wiki/Cache_stampede\">cache stampede</a> (when many requests hit an empty cache at once)</li>\n<li>Tag-based cache invalidation</li>\n<li>Configurable serialization</li>\n<li>Metrics and monitoring</li>\n</ul>\n<p>The L1 cache runs in your application's memory.\nThe L2 cache can be Redis, SQL Server, or any other distributed cache.\nYou can use HybridCache with just the L1 cache if you don't need distributed caching.</p>\n<h2>Installing HybridCache</h2>\n<p>Install the <code>Microsoft.Extensions.Caching.Hybrid</code> NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.Extensions.Caching.Hybrid\n</code></pre>\n<p>Add <code>HybridCache</code> to your services:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHybridCache(options =&gt;\n{\n    // Maximum size of cached items\n    options.MaximumPayloadBytes = 1024 * 1024 * 10; // 10MB\n    options.MaximumKeyLength = 512;\n\n    // Default timeouts\n    options.DefaultEntryOptions = new HybridCacheEntryOptions\n    {\n        Expiration = TimeSpan.FromMinutes(30),\n        LocalCacheExpiration = TimeSpan.FromMinutes(30)\n    };\n});\n</code></pre>\n<p>For custom types, you can add your own serializer:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHybridCache()\n    .AddSerializer&lt;CustomType, CustomSerializer&gt;();\n</code></pre>\n<h2>Using HybridCache</h2>\n<p><code>HybridCache</code> provides several methods to work with cached data.\nThe most important ones are <code>GetOrCreateAsync</code>, <code>SetAsync</code>, and various remove methods.\nLet's see how to use each one in real-world scenarios.</p>\n<h3>Getting or Creating Cache Entries</h3>\n<p>The <code>GetOrCreateAsync</code> method is your main tool for working with cached data.\nIt handles both cache hits and misses automatically.\nIf the data isn't in the cache, it calls your factory method to get the data, caches it, and returns it.</p>\n<p>Here's an endpoint that gets product details:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/products/{id}&quot;, async (\n    int id,\n    HybridCache cache,\n    ProductDbContext db,\n    CancellationToken ct) =&gt;\n{\n    var product = await cache.GetOrCreateAsync(\n        $&quot;product-{id}&quot;,\n        async token =&gt;\n        {\n            return await db.Products\n                .Include(p =&gt; p.Category)\n                .FirstOrDefaultAsync(p =&gt; p.Id == id, token);\n        },\n        cancellationToken: ct\n    );\n\n    return product is null ? Results.NotFound() : Results.Ok(product);\n});\n</code></pre>\n<p>In this example:</p>\n<ul>\n<li>The cache key is unique per product</li>\n<li>If the product is in the cache, it's returned immediately</li>\n<li>If not, the factory method runs to get the data</li>\n<li>Other concurrent requests for the same product wait for the first one to finish</li>\n</ul>\n<h3>Setting Cache Entries Directly</h3>\n<p>Sometimes you need to update the cache directly, like after modifying data.\nThe <code>SetAsync</code> method handles this:</p>\n<pre><code class=\"language-csharp\">app.MapPut(&quot;/products/{id}&quot;, async (int id, Product product, HybridCache cache) =&gt;\n{\n    // First update the database\n    await UpdateProductInDatabase(product);\n\n    // Then update the cache with custom expiration\n    var options = new HybridCacheEntryOptions\n    {\n        Expiration = TimeSpan.FromHours(1),\n        LocalCacheExpiration = TimeSpan.FromMinutes(30)\n    };\n\n    await cache.SetAsync(\n        $&quot;product-{id}&quot;,\n        product,\n        options\n    );\n\n    return Results.NoContent();\n});\n</code></pre>\n<p>Key points about <code>SetAsync</code>:</p>\n<ul>\n<li>It updates both L1 and L2 cache</li>\n<li>You can specify different timeouts for L1 and L2</li>\n<li>It overwrites any existing value for the same key</li>\n</ul>\n<h3>Using Cache Tags</h3>\n<p>Tags are powerful for managing groups of related cache entries.\nYou can invalidate multiple entries at once using tags:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/categories/{id}/products&quot;, async (\n    int id,\n    HybridCache cache,\n    ProductDbContext db,\n    CancellationToken ct) =&gt;\n{\n    var tags = [$&quot;category-{id}&quot;, &quot;products&quot;];\n\n    var products = await cache.GetOrCreateAsync(\n        $&quot;products-by-category-{id}&quot;,\n        async token =&gt;\n        {\n            return await db.Products\n                .Where(p =&gt; p.CategoryId == id)\n                .Include(p =&gt; p.Category)\n                .ToListAsync(token);\n        },\n        tags: tags,\n        cancellationToken: ct\n    );\n\n    return Results.Ok(products);\n});\n\n// Endpoint to invalidate all products in a category\napp.MapPost(&quot;/categories/{id}/invalidate&quot;, async (\n    int id,\n    HybridCache cache,\n    CancellationToken ct) =&gt;\n{\n    await cache.RemoveByTagAsync($&quot;category-{id}&quot;, ct);\n\n    return Results.NoContent();\n});\n</code></pre>\n<p>Tags are useful for:</p>\n<ul>\n<li>Invalidating all products in a category</li>\n<li>Clearing all cached data for a specific user</li>\n<li>Refreshing all related data when something changes</li>\n</ul>\n<h3>Removing Single Entries</h3>\n<p>For direct cache invalidation of specific items, use <code>RemoveAsync</code>:</p>\n<pre><code class=\"language-csharp\">app.MapDelete(&quot;/products/{id}&quot;, async (int id, HybridCache cache) =&gt;\n{\n    // First delete from database\n    await DeleteProductFromDatabase(id);\n\n    // Then remove from cache\n    await cache.RemoveAsync($&quot;product-{id}&quot;);\n\n    return Results.NoContent();\n});\n</code></pre>\n<p><code>RemoveAsync</code>:</p>\n<ul>\n<li>Removes the item from both L1 and L2 cache</li>\n<li>Works immediately, no delay</li>\n<li>Does nothing if the key doesn't exist</li>\n<li>Is safe to call multiple times</li>\n</ul>\n<p>Remember that <code>HybridCache</code> handles all the complexity of distributed caching, serialization, and stampede protection for you.\nYou just need to focus on your <strong>cache keys</strong> and when to invalidate the cache.</p>\n<h2>Adding Redis as L2 Cache</h2>\n<p>To use <a href=\"https://redis.io/\">Redis</a> as your distributed cache:</p>\n<ol>\n<li>Install the <code>Microsoft.Extensions.Caching.StackExchangeRedis</code> NuGet package:</li>\n</ol>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.Extensions.Caching.StackExchangeRedis\n</code></pre>\n<ol start=\"2\">\n<li>Configure Redis and <code>HybridCache</code>:</li>\n</ol>\n<pre><code class=\"language-csharp\">// Add Redis\nbuilder.Services.AddStackExchangeRedisCache(options =&gt;\n{\n    options.Configuration = &quot;your-redis-connection-string&quot;;\n});\n\n// Add HybridCache - it will automatically use Redis as L2\nbuilder.Services.AddHybridCache();\n</code></pre>\n<p><code>HybridCache</code> will automatically detect and use Redis as the L2 cache.</p>\n<h2>Summary</h2>\n<p><code>HybridCache</code> simplifies caching in .NET applications.\nIt combines fast in-memory caching with distributed caching, prevents common problems like <strong>cache stampede</strong>,\nand works well in both single-server and distributed systems.</p>\n<p>Start with the default settings and basic usage patterns - the library is designed to be simple to use while solving complex caching problems.</p>\n<p>One thing <code>HybridCache</code> can't solve for you is the key.\nIf you're caching a value you derive from other data, <a href=\"https://milanjovanovic.tech/blog/content-addressed-cache-dotnet\"><strong>content-addressed caching</strong></a> hashes every input into the key, so an entry can never go stale.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/hybrid-cache-in-aspnetcore-new-caching-library",
            "title": "HybridCache in ASP.NET Core - New Caching Library",
            "summary": "HybridCache in .NET 9 combines fast in-memory caching with distributed caching, solving problems like cache stampede while adding tag-based invalidation.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_116.png",
            "date_modified": "2024-11-16T00:00:00.000Z",
            "date_published": "2024-11-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts",
            "content_html": "<p>C# supports functional programming through records, LINQ, and lambda expressions.\nThe practical parts are the patterns you can use today: higher-order functions, errors as values, monadic binding, pure functions, and immutability.\nThis issue shows each one with concrete C# examples.</p>\n<p>Functional programming patterns can feel academic and abstract.\nTerms like &quot;monads&quot; and &quot;functors&quot; scare many developers away.\nBut beneath the intimidating terminology are practical patterns that can make your code safer and more maintainable.</p>\n<p>C# has embraced many functional programming features over the years.</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>Records</strong></a> for immutability</li>\n<li><strong>LINQ</strong> for functional transformations</li>\n<li>Lambda expressions for first-class functions</li>\n</ul>\n<p>These features aren't just syntax sugar - they help prevent bugs and make code easier to reason about.</p>\n<p>Let's look at five practical patterns you can use in your C# projects today.</p>\n<h2>Higher-Order Functions</h2>\n<p>Higher-order functions can take other functions as parameters or return them as results.\nThey let you write code that's more flexible and composable because you can pass behavior around like data.</p>\n<p>Common examples of higher-order functions are LINQ's <code>Where</code> and <code>Select</code>, which take functions to transform data.</p>\n<p>Let's refactor this validation example with higher-order functions:</p>\n<pre><code class=\"language-csharp\">public class OrderValidator\n{\n    public bool ValidateOrder(Order order)\n    {\n        if (order.Items.Count == 0) return false;\n        if (order.TotalAmount &lt;= 0) return false;\n        if (order.ShippingAddress == null) return false;\n        return true;\n    }\n}\n\n// What if we need:\n// - different validation rules for different countries?\n// - to reuse some validations but not others?\n// - to combine validations differently?\n</code></pre>\n<p>Here's how higher-order functions make this more flexible:</p>\n<pre><code class=\"language-csharp\">public static class OrderValidation\n{\n    public static Func&lt;Order, bool&gt; CreateValidator(string countryCode, decimal minimumOrderValue)\n    {\n        var baseValidations = CombineValidations(\n            o =&gt; o.Items.Count &gt; 0,\n            o =&gt; o.TotalAmount &gt;= minimumOrderValue,\n            o =&gt; o.ShippingAddress != null\n        );\n\n        return countryCode switch\n        {\n            &quot;US&quot; =&gt; CombineValidations(\n                baseValidations,\n                order =&gt; IsValidUSAddress(order.ShippingAddress)),\n            &quot;EU&quot; =&gt; CombineValidations(\n                baseValidations,\n                order =&gt; IsValidVATNumber(order.VatNumber)),\n            _ =&gt; baseValidations\n        };\n    }\n\n    private static Func&lt;Order, bool&gt; CombineValidations(params Func&lt;Order, bool&gt;[] validations) =&gt;\n        order =&gt; validations.All(v =&gt; v(order));\n}\n\n// Usage\nvar usValidator = OrderValidation.CreateValidator(&quot;US&quot;, minimumOrderValue: 25.0m);\nvar euValidator = OrderValidation.CreateValidator(&quot;EU&quot;, minimumOrderValue: 30.0m);\n</code></pre>\n<p>The higher-order function approach makes validators composable, testable, and easy to extend.\nEach validation rule is a simple function that we can compose.</p>\n<h2>Errors as Values</h2>\n<p>Error handling in C# often looks like this:</p>\n<pre><code class=\"language-csharp\">public class UserService\n{\n    public User CreateUser(string email, string password)\n    {\n        if (string.IsNullOrEmpty(email))\n        {\n            throw new ArgumentException(&quot;Email is required&quot;);\n        }\n\n        if (password.Length &lt; 8)\n        {\n            throw new ArgumentException(&quot;Password too short&quot;);\n        }\n\n        if (_userRepository.EmailExists(email))\n        {\n            throw new DuplicateEmailException(email);\n        }\n\n        // Create user...\n    }\n}\n</code></pre>\n<p>The problem?</p>\n<ul>\n<li><a href=\"https://youtu.be/E3dU9Y1CsnI\">Exceptions are expensive</a></li>\n<li>Callers often forget to handle exceptions</li>\n<li>The method signature lies - it claims to return a User but might throw</li>\n</ul>\n<p>We can make errors explicit using the <a href=\"https://github.com/mcintyre321/OneOf\">OneOf</a> library.\nIt provides discriminated unions for C#, using a custom type <code>OneOf&lt;T0, ... Tn&gt;</code>.</p>\n<pre><code class=\"language-csharp\">public class UserService\n{\n    public OneOf&lt;User, ValidationError, DuplicateEmailError&gt; CreateUser(string email, string password)\n    {\n        if (string.IsNullOrEmpty(email))\n        {\n            return new ValidationError(&quot;Email is required&quot;);\n        }\n\n        if (password.Length &lt; 8)\n        {\n            return new ValidationError(&quot;Password too short&quot;);\n        }\n\n        if (_userRepository.EmailExists(email))\n        {\n            return new DuplicateEmailError(email);\n        }\n\n        return new User(email, password);\n    }\n}\n</code></pre>\n<p>By making the errors explicit:</p>\n<ul>\n<li>The method signature tells the whole truth</li>\n<li>Callers must handle all possible outcomes</li>\n<li>No performance overhead from exceptions</li>\n<li>The flow is easier to follow</li>\n</ul>\n<p>Here's how you use it:</p>\n<pre><code class=\"language-csharp\">var result = userService.CreateUser(email, password);\n\nresult.Switch(\n    user =&gt; SendWelcomeEmail(user),\n    validationError =&gt; HandleError(validationError),\n    duplicateError =&gt; HandleError(duplicateError)\n);\n</code></pre>\n<h2>Monadic Binding</h2>\n<p>A <strong>monad</strong> is a container for values - like <code>List&lt;T&gt;</code>, <code>IEnumerable&lt;T&gt;</code>, or <code>Task&lt;T&gt;</code>.\nWhat makes it special is that you can chain operations on the contained values without dealing with the container directly.\nThis chaining is called monadic binding.</p>\n<p>You use monadic binding daily with LINQ, but you might not know it.\nIt's what allows us to chain operations that transform data.</p>\n<p>Map (<code>Select</code>) transforms values:</p>\n<pre><code class=\"language-csharp\">// Simple transformations with Select (Map)\nvar numbers = new[] { 1, 2, 3, 4 };\n\nvar doubled = numbers.Select(x =&gt; x * 2);\n</code></pre>\n<p>Bind (<code>SelectMany</code>) transforms and flattens:</p>\n<pre><code class=\"language-csharp\">// Operations that return multiple values use SelectMany (Bind)\nvar folders = new[] { &quot;docs&quot;, &quot;photos&quot; };\n\nvar files = folders.SelectMany(folder =&gt; Directory.GetFiles(folder));\n</code></pre>\n<p>A popular example of applying monads in practice is the <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>Result pattern</strong></a>,\nwhich provides a clean way to chain operations that might fail.</p>\n<h2>Pure Functions</h2>\n<p>Pure functions are predictable: they depend only on their inputs and don't change anything in the system.\nNo database calls, no API requests, no global state.\nThis constraint makes them easier to understand, test, and debug.</p>\n<pre><code class=\"language-csharp\">// Impure - relies on hidden state\npublic class PriceCalculator\n{\n    private decimal _taxRate;\n    private List&lt;Discount&gt; _activeDiscounts;\n\n    public decimal CalculatePrice(Order order)\n    {\n        var price = order.Items.Sum(i =&gt; i.Price);\n\n        foreach (var discount in _activeDiscounts)\n        {\n            price -= discount.Calculate(price);\n        }\n\n        return price * (1 + _taxRate);\n    }\n}\n</code></pre>\n<p>Here's the same example as a pure function:</p>\n<pre><code class=\"language-csharp\">// Pure - everything is explicit\npublic static class PriceCalculator\n{\n    public static decimal CalculatePrice(\n        Order order,\n        decimal taxRate,\n        IReadOnlyList&lt;Discount&gt; discounts)\n    {\n        var basePrice = order.Items.Sum(i =&gt; i.Price);\n\n        var afterDiscounts = discounts.Aggregate(\n            basePrice,\n            (price, discount) =&gt; price - discount.Calculate(price));\n\n        return afterDiscounts * (1 + taxRate);\n    }\n}\n</code></pre>\n<p>Pure functions are thread-safe, easy to test, and simple to reason about because all dependencies are explicit.</p>\n<h2>Immutability</h2>\n<p><strong>Immutable objects</strong> can't be changed after creation.\nInstead, they create new instances for every change.\nThis simple constraint eliminates entire categories of bugs: race conditions, accidental modifications, and inconsistent state.</p>\n<p>Here's an example of a mutable type:</p>\n<pre><code class=\"language-csharp\">public class Order\n{\n    public List&lt;OrderItem&gt; Items { get; set; }\n    public decimal Total { get; set; }\n    public OrderStatus Status { get; set; }\n\n    public void AddItem(OrderItem item)\n    {\n        Items.Add(item);\n        Total += item.Price;\n        // Bug: Thread safety issues\n        // Bug: Can modify shipped orders\n        // Bug: Total might not match Items\n    }\n}\n</code></pre>\n<p>Let's make this an immutable type:</p>\n<pre><code class=\"language-csharp\">public record Order\n{\n    public ImmutableList&lt;OrderItem&gt; Items { get; init; }\n    public OrderStatus Status { get; init; }\n    public decimal Total =&gt; Items.Sum(x =&gt; x.Price);\n\n    public Order AddItem(OrderItem item)\n    {\n        if (Status != OrderStatus.Created)\n        {\n            throw new InvalidOperationException(&quot;Can't modify shipped orders&quot;);\n        }\n\n        return this with\n        {\n            Items = Items.Add(item)\n        };\n    }\n}\n</code></pre>\n<p>The immutable version:</p>\n<ul>\n<li>Is thread-safe by default</li>\n<li>Makes invalid states impossible</li>\n<li>Keeps data and calculations consistent</li>\n<li>Makes changes explicit and traceable</li>\n</ul>\n<h2>Takeaway</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/how-to-apply-functional-programming-in-csharp\"><strong>Functional programming</strong></a> isn't just about writing &quot;cleaner&quot; code.\nThese patterns fundamentally change how you handle complexity:</p>\n<ul>\n<li><strong>Push errors to compile time</strong> - Catch problems before running the code</li>\n<li><strong>Make invalid states impossible</strong> - Don't rely on documentation or conventions</li>\n<li><strong>Make the happy path obvious</strong> - When everything is explicit, the flow is clear</li>\n</ul>\n<p>You can adopt these patterns gradually.\nStart with one class, one module, one feature.\nThe goal isn't to write purely functional code.\nThe goal is to write code that's safer, more predictable, and easier to maintain.</p>\n<p>Hope this was helpful.\nSee you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts",
            "title": "Functional Programming in C#: The Practical Parts",
            "summary": "Functional programming patterns can make your C# code safer and more maintainable, without getting lost in academic theory.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_115.png",
            "date_modified": "2024-11-09T00:00:00.000Z",
            "date_published": "2024-11-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/clean-architecture-the-missing-chapter",
            "content_html": "<p>Clean Architecture is about managing dependencies, and the famous diagram was never meant to be a project structure.\nSimon Brown's &quot;missing chapter&quot; for the Clean Architecture book shows how to organize code around business capabilities, using package by feature or package by component.\nThis issue covers both approaches and how to enforce the boundaries in .NET.</p>\n<p>I see the same mistake happen over and over again.</p>\n<p>Developers discover <a href=\"https://milanjovanovic.tech/blog/clean-architecture-dotnet\"><strong>Clean Architecture</strong></a>, get excited about its principles, and then... they turn the famous Clean Architecture diagram into a project structure.</p>\n<p>But here's the thing: <strong>Clean Architecture is not about folders</strong>.\nIt's about dependencies.</p>\n<p>Simon Brown wrote a &quot;missing chapter&quot; for Uncle Bob's Clean Architecture book that addresses exactly this issue.\nYet somehow, this crucial message got lost along the way.</p>\n<p>Today, I'll show you what Uncle Bob's Clean Architecture diagram really means and how you should actually organize your code.\nWe'll look at practical examples that you can use in your projects right now.</p>\n<p>Let's clear up this common misconception once and for all.</p>\n<h2>The Problem With Traditional Layering</h2>\n<p>Almost every .NET developer has built a solution that looks like this:</p>\n<ul>\n<li><code>MyApp.Web</code> for controllers and views</li>\n<li><code>MyApp.Business</code> for services and business logic</li>\n<li><code>MyApp.Data</code> for repositories and data access</li>\n</ul>\n<p>It's the default approach. It's what we see in tutorials. It's what we teach juniors.</p>\n<p>And it's completely wrong.</p>\n<h3>Why Layer-Based Organization Fails</h3>\n<p>When you organize code by technical layers, you scatter related components across multiple projects.\nA single feature, like managing policies, ends up spread across your entire codebase:</p>\n<ul>\n<li>Policies controller in the Web layer</li>\n<li>Policy service in the Business layer</li>\n<li>Policy repository in the Data layer</li>\n</ul>\n<p>Here's what you'll see when looking at the folder structure:</p>\n<pre><code class=\"language-text\">📁 MyApp.Web\n|__ 📁 Controllers\n    |__ #️⃣ PoliciesController.cs\n📁 MyApp.Business\n|__ 📁 Services\n    |__ #️⃣ PolicyService.cs\n📁 MyApp.Data\n|__ 📁 Repositories\n    |__ #️⃣ PolicyRepository.cs\n</code></pre>\n<p>Here's a visual representation of the layer-based architecture:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_114/layered_architecture.png\" alt=\"Feature Scattering in Layer-Based Architecture.\">\n</div>\n<p>This fragmentation creates several problems:</p>\n<ol>\n<li>\n<p><strong>Violates Common Closure Principle</strong> - Classes that change together should stay together.\nWhen your &quot;Policies&quot; feature changes, you're touching three different projects.</p>\n</li>\n<li>\n<p><strong>Hidden dependencies</strong> - Public interfaces everywhere make it possible to bypass layers.\nNothing stops a controller from directly accessing a repository.</p>\n</li>\n<li>\n<p><strong>No business intent</strong> - Opening your solution tells you nothing about what the application does.\nIt only shows technical implementation details.</p>\n</li>\n<li>\n<p><strong>Harder maintenance</strong> - Making changes requires jumping between multiple projects.</p>\n</li>\n</ol>\n<p>The worst part? This approach doesn't even achieve what it promises.\nDespite the separate projects, you often end up with a &quot;big ball of mud&quot; because public access modifiers allow any class to reference any other class.</p>\n<h3>The Real Intent of Layers</h3>\n<p>Clean Architecture's circles were never meant to represent projects or folders.\nThey represent different levels of policy, with <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>dependencies pointing inward</strong></a> toward business rules.</p>\n<p>You can achieve this without splitting your code into artificial technical layers.</p>\n<p>Let me show you a better way.</p>\n<h2>Better Approaches to Code Organization</h2>\n<p>Instead of splitting your code by technical layers, you have two better options: <strong>package by feature</strong> or <strong>package by component</strong>.</p>\n<p>Let's look at both.</p>\n<h3>Package by Feature</h3>\n<p>Organizing by feature is a solid option.\nEach feature gets its own namespace and contains everything needed to implement that feature.</p>\n<pre><code class=\"language-text\">📁 MyApp.Policies\n|__ 📁 RenewPolicy\n    |__ #️⃣ RenewPolicyCommand.cs\n    |__ #️⃣ RenewPolicyHandler.cs\n    |__ #️⃣ PolicyValidator.cs\n    |__ #️⃣ PolicyRepository.cs\n|__ 📁 ViewPolicyHistory\n    |__ #️⃣ PolicyHistoryQuery.cs\n    |__ #️⃣ PolicyHistoryHandler.cs\n    |__ #️⃣ PolicyHistoryViewModel.cs\n</code></pre>\n<p>Here's a diagram representing this structure:</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_114/feature_folder_architecture.png\" alt=\"Vertical slice architecture for package by feature organization.\">\n</div>\n<p>This approach:</p>\n<ul>\n<li>Makes features explicit</li>\n<li>Keeps related code together</li>\n<li>Simplifies navigation</li>\n<li>Makes it easier to maintain and modify features</li>\n</ul>\n<p>If you want to learn more, check out my article about <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>vertical slice architecture</strong></a>.</p>\n<h3>Package by Component</h3>\n<p>A component is a cohesive group of related functionality with a well-defined interface.\nComponent-based organization is more coarse-grained than feature folders.\nThink of it as a mini application that handles one specific business capability.</p>\n<p>This is very similar to how I define modules in a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>.</p>\n<p>Here's what a component-based organization looks like:</p>\n<pre><code class=\"language-text\">📁 MyApp.Web\n|__ 📁 Controllers\n    |__ #️⃣ PoliciesController.cs\n📁 MyApp.Policies\n|__ #️⃣ PoliciesComponent.cs     // Public interface\n|__ #️⃣ PolicyService.cs         // Implementation detail\n|__ #️⃣ PolicyRepository.cs      // Implementation detail\n</code></pre>\n<p>The key difference? Only <code>PoliciesComponent</code> is public.\nEverything else is internal to the component.</p>\n<div className=\"centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_114/component_architecture.png\" alt=\"Feature Scattering in Layer-Based Architecture.\">\n</div>\n<p>This means:</p>\n<ul>\n<li>No bypassing layers</li>\n<li>Clear dependencies</li>\n<li>Real encapsulation</li>\n<li>Business intent visible in the structure</li>\n</ul>\n<h3>Which One Should You Choose?</h3>\n<p>Choose <strong>Package by Feature</strong> when:</p>\n<ul>\n<li>You have many small, independent features</li>\n<li>Your features don't share much code</li>\n<li>You want maximum flexibility</li>\n</ul>\n<p>Choose <strong>Package by Component</strong> when:</p>\n<ul>\n<li>You have clear business capabilities</li>\n<li>You want strong encapsulation</li>\n<li>You might split into microservices later</li>\n</ul>\n<p>Both approaches achieve what Clean Architecture really wants: proper dependency management and business focus.</p>\n<p>Here's a side-by-side comparison of these architectural approaches:</p>\n<figure>\n  <div className=\"centered\">\n    <img src=\"https://milanjovanovic.tech/blogs/mnw_114/architecture_comparison.png\" alt=\"Comparison between layered, vertical slice and component architectural approaches.\">\n  </div>\n  <figcaption>\n    Greyed-out types are internal to the defining assembly.\n  </figcaption>\n</figure>\n<p>In the Missing Chapter of Clean Architecture, Simon Brown argues strongly for package by component.\nThe key insight is that components are the natural way to slice a system.\nThey represent complete business capabilities, not just technical features.</p>\n<p>My recommendation?\nStart with package by component.\nWithin the component, organize around features.</p>\n<h2>Practical Examples</h2>\n<p>Let's transform a typical layered application into a clean, component-based structure.\nWe'll use an insurance policy system as an example.</p>\n<h3>The Traditional Way</h3>\n<p>Here's how most developers structure their solution:</p>\n<pre><code class=\"language-csharp\">// MyApp.Data\npublic interface IPolicyRepository\n{\n    Task&lt;Policy&gt; GetByIdAsync(string policyNumber);\n    Task SaveAsync(Policy policy);\n}\n\n// MyApp.Business\npublic class PolicyService : IPolicyService\n{\n    private readonly IPolicyRepository _repository;\n\n    public PolicyService(IPolicyRepository repository)\n    {\n        _repository = repository;\n    }\n\n    public async Task RenewPolicyAsync(string policyNumber)\n    {\n        var policy = await _repository.GetByIdAsync(policyNumber);\n        // Business logic here\n        await _repository.SaveAsync(policy);\n    }\n}\n\n// MyApp.Web\npublic class PoliciesController : ControllerBase\n{\n    private readonly IPolicyService _policyService;\n\n    public PoliciesController(IPolicyService policyService)\n    {\n        _policyService = policyService;\n    }\n\n    [HttpPost(&quot;renew/{policyNumber}&quot;)]\n    public async Task&lt;IActionResult&gt; RenewPolicy(string policyNumber)\n    {\n        await _policyService.RenewPolicyAsync(policyNumber);\n        return Ok();\n    }\n}\n</code></pre>\n<p>The problem?\nEverything is public.\nAny class can bypass the service and go straight to the repository.</p>\n<h3>The Clean Way</h3>\n<p>Here's the same functionality organized as a proper component:</p>\n<pre><code class=\"language-csharp\">// The only public contract\npublic interface IPoliciesComponent\n{\n    Task RenewPolicyAsync(string policyNumber);\n}\n\n// Everything below is internal to the component\ninternal class PoliciesComponent : IPoliciesComponent\n{\n    private readonly IRenewPolicyHandler _renewPolicyHandler;\n\n    // Public constructor for DI\n    public PoliciesComponent(IRenewPolicyHandler renewPolicyHandler)\n    {\n        _renewPolicyHandler = renewPolicyHandler;\n    }\n\n    public async Task RenewPolicyAsync(string policyNumber)\n    {\n        await _renewPolicyHandler.HandleAsync(policyNumber);\n    }\n}\n\ninternal interface IRenewPolicyHandler\n{\n    Task HandleAsync(string policyNumber);\n}\n\ninternal class RenewPolicyHandler : IRenewPolicyHandler\n{\n    private readonly IPolicyRepository _repository;\n\n    internal RenewPolicyHandler(IPolicyRepository repository)\n    {\n        _repository = repository;\n    }\n\n    public async Task HandleAsync(string policyNumber)\n    {\n        var policy = await _repository.GetByIdAsync(policyNumber);\n        // Business logic for policy renewal here\n        await _repository.SaveAsync(policy);\n    }\n}\n\ninternal interface IPolicyRepository\n{\n    Task&lt;Policy&gt; GetByIdAsync(string policyNumber);\n    Task SaveAsync(Policy policy);\n}\n</code></pre>\n<p>The key improvements are:</p>\n<ol>\n<li>\n<p><strong>Single public interface</strong> - Only <code>IPoliciesComponent</code> is public. Everything else is internal.</p>\n</li>\n<li>\n<p><strong>Protected dependencies</strong> - No way to bypass the component and access the repository directly.</p>\n</li>\n<li>\n<p><strong>Clear dependencies</strong> - All dependencies flow inward through the component.</p>\n</li>\n<li>\n<p><strong>Proper encapsulation</strong> - Implementation details are truly hidden.</p>\n</li>\n</ol>\n<p>This is how you would register the services with dependency injection:</p>\n<pre><code class=\"language-csharp\">services.AddScoped&lt;IPoliciesComponent, PoliciesComponent&gt;();\nservices.AddScoped&lt;IRenewPolicyHandler, RenewPolicyHandler&gt;();\nservices.AddScoped&lt;IPolicyRepository, SqlPolicyRepository&gt;();\n</code></pre>\n<p>This structure enforces Clean Architecture principles through compiler-checked boundaries, not just conventions.</p>\n<p>The compiler won't let you bypass the component's public interface.\nThat's much stronger than hoping developers follow the rules.</p>\n<h2>Best Practices and Limitations</h2>\n<p>Let's discuss something that is often overlooked: the practical limitations of enforcing Clean Architecture in .NET.</p>\n<h3>The Limits of Encapsulation</h3>\n<p>The <code>internal</code> keyword in .NET provides protection within a single assembly.\nHere's what that means in practice:</p>\n<pre><code class=\"language-csharp\">// In a single project:\npublic interface IPoliciesComponent { } // Public contract\ninternal class PoliciesComponent : IPoliciesComponent { }\ninternal class PolicyRepository { }\n\n// Someone could still do this:\npublic class BadPoliciesComponent : IPoliciesComponent\n{\n    public BadPoliciesComponent()\n    {\n        // Nothing stops them from creating a bad implementation\n    }\n}\n</code></pre>\n<p>While <code>internal</code> helps, it doesn't prevent all architectural violations.</p>\n<h3>The Trade-offs</h3>\n<p>Some teams split their code into separate assemblies for stronger encapsulation:</p>\n<pre><code class=\"language-plaintext\">MyCompany.Policies.Core.dll\nMyCompany.Policies.Infrastructure.dll\nMyCompany.Policies.Api.dll\n</code></pre>\n<p>This comes with trade-offs:</p>\n<ol>\n<li><strong>More complex build process</strong> - Multiple projects need to be compiled and referenced.</li>\n<li><strong>Harder navigation</strong> - Jumping between assemblies in the IDE is slower.</li>\n<li><strong>Deployment complexity</strong> - More DLLs to manage and deploy.</li>\n</ol>\n<h3>A Pragmatic Approach</h3>\n<p>Here's what I recommend:</p>\n<ol>\n<li>\n<p><strong>Use a single assembly</strong></p>\n<ul>\n<li>Keep related code together</li>\n<li>Use <code>internal</code> for implementation details</li>\n<li>Make only the component interfaces public</li>\n<li>Add <code>sealed</code> to prevent inheritance when possible</li>\n</ul>\n</li>\n<li>\n<p><strong>Enforce through architecture testing</strong></p>\n<ul>\n<li>Add architecture tests to verify dependencies</li>\n<li>Automatically check for architectural violations</li>\n<li>Fail the build if someone bypasses the rules</li>\n</ul>\n</li>\n</ol>\n<pre><code class=\"language-csharp\">[Fact]\npublic void Controllers_Should_Only_Depend_On_Component_Interfaces()\n{\n    var allTypes = Types.InAssembly(Assembly.GetExecutingAssembly());\n\n    TestResult? result = allTypes\n        .That()\n        .ResideInNamespace(&quot;MyApp.Controllers&quot;)\n        .Should()\n        .OnlyHaveDependenciesOn(\n            allTypes\n                .That()\n                .HaveNameEndingWith(&quot;Component&quot;)\n                .Or()\n                .HaveNameStartingWith(&quot;IPolicy&quot;)\n                .GetTypes()\n                .Select(t =&gt; t.FullName!)\n                .ToArray())\n        .GetResult();\n\n    result.IsSuccessful.Should().BeTrue();\n}\n</code></pre>\n<p>Want to learn more about enforcing architecture through testing?\nCheck out my article on <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>architecture testing</strong></a>.</p>\n<p>Remember: Clean Architecture is about managing dependencies, not about achieving perfect encapsulation.\nUse the tools the language gives you, but don't over-complicate things chasing an impossible ideal.</p>\n<h2>Conclusion</h2>\n<p>Clean Architecture isn't about projects, folders, or perfect encapsulation.</p>\n<p>It's about:</p>\n<ul>\n<li>Organizing code around business capabilities</li>\n<li>Managing dependencies effectively</li>\n<li>Keeping related code together</li>\n<li>Making boundaries explicit</li>\n</ul>\n<p>Start with a single project.\nUse components.\nMake interfaces public and implementations internal.\nAdd architecture tests if you need more control.</p>\n<p>And remember: <strong>pragmatism beats purism</strong>.\nYour architecture should help you ship features faster, not slow you down with artificial constraints.</p>\n<p>Want to learn more?\nCheck out my <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a> course,\nwhere I'll show you how to build maintainable applications with proper boundaries, clear dependencies, and business-focused components.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/clean-architecture-the-missing-chapter",
            "title": "Clean Architecture: The Missing Chapter",
            "summary": "Clean Architecture's famous diagram is often misinterpreted as a project structure, scattering business logic across artificial technical layers.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_114.png",
            "date_modified": "2024-11-02T00:00:00.000Z",
            "date_published": "2024-11-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-idempotent-rest-apis-in-aspnetcore",
            "content_html": "<p>An idempotent REST API returns the same result no matter how many times a client repeats a request.\nIn ASP.NET Core, you can implement it with idempotency keys: the client sends a unique key in a header, and the server caches the response so retries execute the operation only once.\nThis issue shows implementations for both controllers and Minimal APIs.</p>\n<p>Idempotency is a crucial concept for REST APIs that ensures the reliability and consistency of your system.\nAn idempotent operation can be repeated multiple times without changing the result beyond the initial API request.\nThis property is especially important in distributed systems, where network failures or timeouts can lead to repeated requests.</p>\n<p>Implementing idempotency in your API brings several benefits:</p>\n<ul>\n<li>It prevents unintended duplicate operations</li>\n<li>It improves reliability in distributed systems</li>\n<li>It helps handle network issues and retries gracefully</li>\n</ul>\n<p>In this week's issue, we'll explore how to implement idempotency in ASP.NET Core APIs, ensuring your system remains robust and reliable.</p>\n<h2>What is Idempotence?</h2>\n<p>Idempotence, in the context of web APIs, means that making multiple identical requests should have the same effect as making a single request.\nIn other words, no matter how many times a client sends the same request, the server-side effect should only occur once.</p>\n<p>The <a href=\"https://www.rfc-editor.org/rfc/rfc9110\">RFC 9110</a> standard about HTTP Semantics offers a definition we could use.\nHere's what it says about <strong>idempotent methods</strong>:</p>\n<figure>\n<blockquote>\n<p>A request method is considered &quot;idempotent&quot; if the intended effect on the server of multiple identical requests with\nthat method is the same as the effect for a single such request.</p>\n<p>Of the request methods defined by this specification,\nPUT, DELETE, and safe request methods [(GET, HEAD, OPTIONS, and TRACE) - author's note] are idempotent.</p>\n</blockquote>\n  <figcaption>\n    _&mdash; <a href=\"https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2-1\">RFC 9110 (HTTP Semantics), Section 9.2.2, Paragraph 1</a>_\n  </figcaption>\n</figure>\n<p>However, the following paragraph is quite interesting.\nIt clarifies that the server can implement &quot;other non-idempotent side effects&quot; that don't apply to the resource.</p>\n<figure>\n<blockquote>\n<p>... the idempotent property only applies to what has been requested by the user;\na server is free to log each request separately, retain a revision control history, or implement other non-idempotent side effects for each idempotent request.</p>\n</blockquote>\n  <figcaption>\n    _&mdash; <a href=\"https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2-2\">RFC 9110 (HTTP Semantics), Section 9.2.2, Paragraph 2</a>_\n  </figcaption>\n</figure>\n<p>The benefits of implementing idempotency extend beyond just adhering to HTTP method semantics.\nIt significantly improves the reliability of your API, especially in distributed systems where network issues can lead to retried requests.\nBy implementing idempotency, you prevent duplicate operations that could occur due to client retries.</p>\n<h2>Which HTTP Methods are Idempotent?</h2>\n<p>Several HTTP methods are inherently idempotent:</p>\n<ul>\n<li><code>GET</code>, <code>HEAD</code>: Retrieve data without modifying the server state.</li>\n<li><code>PUT</code>: Update a resource, resulting in the same state regardless of repetition.</li>\n<li><code>DELETE</code>: Remove a resource with the same outcome for multiple requests.</li>\n<li><code>OPTIONS</code>: Retrieve communication options information.</li>\n</ul>\n<p><code>POST</code> is not inherently idempotent, as it typically creates resources or processes data.\nRepeated <code>POST</code> requests could create multiple resources or trigger multiple actions.</p>\n<p>However, we can implement idempotency for <code>POST</code> methods using custom logic.</p>\n<p><strong>Note</strong>: While <code>POST</code> requests aren't naturally idempotent, we can design them to be.\nFor example, checking for existing resources before creation ensures that repeated <code>POST</code> requests don't result in duplicate actions or resources.</p>\n<h2>Implementing Idempotency in ASP.NET Core</h2>\n<p>To implement idempotency, we'll use a strategy involving <strong>idempotency keys</strong>:</p>\n<ol>\n<li>The client generates a unique key for each operation and sends it in a custom header.</li>\n<li>The server checks if it has seen this key before:\n<ul>\n<li>For a new key, process the request and store the result.</li>\n<li>For a known key, return the stored result without reprocessing.</li>\n</ul>\n</li>\n</ol>\n<p>This ensures that retried requests (e.g., due to network issues) are processed only once on the server.</p>\n<p>We can implement idempotency for controllers by combining an <code>Attribute</code> and <code>IAsyncActionFilter</code>.\nNow, we can specify the <code>IdempotentAttribute</code> to apply idempotency to a controller endpoint.</p>\n<p><strong>Note</strong>: When a request fails (returns 4xx/5xx), we don't cache the response.\nThis allows clients to retry with the same idempotency key.\nHowever, this means a failed request followed by a successful one with the same key will succeed - make sure this aligns with your business requirements.</p>\n<pre><code class=\"language-csharp\">[AttributeUsage(AttributeTargets.Method)]\ninternal sealed class IdempotentAttribute : Attribute, IAsyncActionFilter\n{\n    private const int DefaultCacheTimeInMinutes = 60;\n    private readonly TimeSpan _cacheDuration;\n\n    public IdempotentAttribute(int cacheTimeInMinutes = DefaultCacheTimeInMinutes)\n    {\n        _cacheDuration = TimeSpan.FromMinutes(cacheTimeInMinutes);\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // Parse the Idempotence-Key header from the request\n        if (!context.HttpContext.Request.Headers.TryGetValue(\n                &quot;Idempotence-Key&quot;,\n                out StringValues idempotenceKeyValue) ||\n            !Guid.TryParse(idempotenceKeyValue, out Guid idempotenceKey))\n        {\n            context.Result = new BadRequestObjectResult(&quot;Invalid or missing Idempotence-Key header&quot;);\n            return;\n        }\n\n        IDistributedCache cache = context.HttpContext\n            .RequestServices.GetRequiredService&lt;IDistributedCache&gt;();\n\n        // Check if we already processed this request and return a cached response (if it exists)\n        string cacheKey = $&quot;Idempotent_{idempotenceKey}&quot;;\n        string? cachedResult = await cache.GetStringAsync(cacheKey);\n        if (cachedResult is not null)\n        {\n            IdempotentResponse response = JsonSerializer.Deserialize&lt;IdempotentResponse&gt;(cachedResult)!;\n\n            var result = new ObjectResult(response.Value) { StatusCode = response.StatusCode };\n            context.Result = result;\n\n            return;\n        }\n\n        // Execute the request and cache the response for the specified duration\n        ActionExecutedContext executedContext = await next();\n\n        if (executedContext.Result is ObjectResult { StatusCode: &gt;= 200 and &lt; 300 } objectResult)\n        {\n            int statusCode = objectResult.StatusCode ?? StatusCodes.Status200OK;\n            IdempotentResponse response = new(statusCode, objectResult.Value);\n\n            await cache.SetStringAsync(\n                cacheKey,\n                JsonSerializer.Serialize(response),\n                new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = _cacheDuration }\n            );\n        }\n    }\n}\n\ninternal sealed class IdempotentResponse\n{\n    [JsonConstructor]\n    public IdempotentResponse(int statusCode, object? value)\n    {\n        StatusCode = statusCode;\n        Value = value;\n    }\n\n    public int StatusCode { get; }\n    public object? Value { get; }\n}\n</code></pre>\n<p><strong>Note</strong>: There's a small <a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\"><strong>race condition</strong></a> window between checking and setting the cache.\nFor absolute consistency, we should consider using a <strong>distributed lock</strong> pattern, though this adds complexity and latency.</p>\n<p>Now, we can apply this attribute to our controller actions:</p>\n<pre><code class=\"language-csharp\">[ApiController]\n[Route(&quot;api/[controller]&quot;)]\npublic class OrdersController : ControllerBase\n{\n    [HttpPost]\n    [Idempotent(cacheTimeInMinutes: 60)]\n    public IActionResult CreateOrder([FromBody] CreateOrderRequest request)\n    {\n        // Process the order...\n\n        return CreatedAtAction(nameof(GetOrder), new { id = orderDto.Id }, orderDto);\n    }\n}\n</code></pre>\n<p><strong>Idempotency with Minimal APIs</strong></p>\n<p>To implement idempotency with Minimal APIs, we can use an <code>IEndpointFilter</code>.</p>\n<pre><code class=\"language-csharp\">internal sealed class IdempotencyFilter(int cacheTimeInMinutes = 60)\n    : IEndpointFilter\n{\n    public async ValueTask&lt;object?&gt; InvokeAsync(\n        EndpointFilterInvocationContext context,\n        EndpointFilterDelegate next)\n    {\n        // Parse the Idempotence-Key header from the request\n        if (TryGetIdempotenceKey(out Guid idempotenceKey))\n        {\n            return Results.BadRequest(&quot;Invalid or missing Idempotence-Key header&quot;);\n        }\n\n        IDistributedCache cache = context.HttpContext\n            .RequestServices.GetRequiredService&lt;IDistributedCache&gt;();\n\n        // Check if we already processed this request and return a cached response (if it exists)\n        string cacheKey = $&quot;Idempotent_{idempotenceKey}&quot;;\n        string? cachedResult = await cache.GetStringAsync(cacheKey);\n        if (cachedResult is not null)\n        {\n            IdempotentResponse response = JsonSerializer.Deserialize&lt;IdempotentResponse&gt;(cachedResult)!;\n            return new IdempotentResult(response.StatusCode, response.Value);\n        }\n\n        object? result = await next(context);\n\n        // Execute the request and cache the response for the specified duration\n        if (result is IStatusCodeHttpResult { StatusCode: &gt;= 200 and &lt; 300 } statusCodeResult\n            and IValueHttpResult valueResult)\n        {\n            int statusCode = statusCodeResult.StatusCode ?? StatusCodes.Status200OK;\n            IdempotentResponse response = new(statusCode, valueResult.Value);\n\n            await cache.SetStringAsync(\n                cacheKey,\n                JsonSerializer.Serialize(response),\n                new DistributedCacheEntryOptions\n                {\n                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(cacheTimeInMinutes)\n                }\n            );\n        }\n\n        return result;\n    }\n}\n\n// We have to implement a custom result to write the status code\ninternal sealed class IdempotentResult : IResult\n{\n    private readonly int _statusCode;\n    private readonly object? _value;\n\n    public IdempotentResult(int statusCode, object? value)\n    {\n        _statusCode = statusCode;\n        _value = value;\n    }\n\n    public Task ExecuteAsync(HttpContext httpContext)\n    {\n        httpContext.Response.StatusCode = _statusCode;\n\n        return httpContext.Response.WriteAsJsonAsync(_value);\n    }\n}\n</code></pre>\n<p>Now, we can apply this endpoint filter to our Minimal API endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;/api/orders&quot;, CreateOrder)\n    .RequireAuthorization()\n    .WithOpenApi()\n    .AddEndpointFilter&lt;IdempotencyFilter&gt;();\n</code></pre>\n<p>An alternative to the previous two implementations is implementing idempotency logic in a custom middleware.</p>\n<h2>Best Practices and Considerations</h2>\n<p>Here are the key things I always keep in mind when implementing idempotency.</p>\n<p>Cache duration is tricky.\nI aim to cover reasonable retry windows without holding onto stale data.\nA reasonable cache time typically ranges from a few minutes to 24-48 hours, depending on your specific use case.</p>\n<p>Concurrency can be a pain, especially in high-traffic APIs.\nA thread-safe implementation using a distributed lock works great.\nIt keeps things in check when multiple requests hit at once.\nBut this should be a rare occurrence.</p>\n<p>For distributed setups, Redis is my go-to.\nIt's perfect as a shared cache, keeping idempotency consistent across all your API instances.\nPlus, it handles distributed locking.</p>\n<p>What if a client reuses an idempotency key with a different request body?\nI return an error in this case.\nMy approach is to hash the request body and store it with the idempotency key.\nWhen a request comes in, I compare the request body hashes.\nIf they differ, I return an error.\nThis prevents misuse of idempotency keys and maintains the integrity of your API.</p>\n<h2>Summary</h2>\n<p>Implementing idempotency in REST APIs enhances service reliability and consistency.\nIt ensures identical requests yield the same result, preventing unintended duplicates and gracefully handling network issues.</p>\n<p>While our implementation provides a foundation, I recommend adapting it to your needs.\nFocus on critical operations in your APIs, especially those that modify the system state or trigger important business processes.</p>\n<p>By embracing idempotency, you're building more robust and user-friendly APIs.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-idempotent-rest-apis-in-aspnetcore",
            "title": "Implementing Idempotent REST APIs in ASP.NET Core",
            "summary": "Learn how to implement idempotency in ASP.NET Core Web APIs to improve reliability and prevent duplicate operations in distributed systems.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_113.png",
            "date_modified": "2024-10-26T00:00:00.000Z",
            "date_published": "2024-10-26T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis",
            "content_html": "<p><strong>Problem Details</strong> is a machine-readable JSON format for describing errors in HTTP API responses, standardized by RFC 9457.\nIn ASP.NET Core, you call <code>AddProblemDetails</code> and <code>UseExceptionHandler</code>, and unhandled exceptions become <code>application/problem+json</code> responses.\nThis issue also covers the .NET 8 <code>IExceptionHandler</code> and how to customize every Problem Details response.</p>\n<p>When developing HTTP APIs, providing consistent and informative error responses is crucial for a smooth developer experience.\n<strong>Problem Details</strong> in ASP.NET Core offers a standardized solution to this challenge, ensuring your APIs communicate errors effectively and uniformly.</p>\n<p>In this article, we'll explore the latest developments in <strong>Problem Details</strong>, including:</p>\n<ul>\n<li>The new <a href=\"https://www.rfc-editor.org/rfc/rfc9457\">RFC 9457</a> that refines the Problem Details standard</li>\n<li>Using the .NET 8 <code>IExceptionHandler</code> for global exception handling</li>\n<li>Using the <code>IProblemDetailsService</code> for customizing Problem Details</li>\n</ul>\n<p>Let's dive into these features and see how they can improve your API's error handling.</p>\n<h2>Understanding Problem Details</h2>\n<p>Problem Details is a machine-readable format for specifying errors in HTTP API responses.\n<a href=\"https://milanjovanovic.tech/blog/rest-api-http-status-codes\"><strong>HTTP status codes</strong></a> don't always contain enough details about errors to be helpful.\nThe Problem Details specification defines a JSON (and XML) document format to describe problems.</p>\n<p>Problem Details includes:</p>\n<ul>\n<li><code>type</code>: A URI reference that identifies the problem type</li>\n<li><code>title</code>: A short, human-readable summary of the problem type</li>\n<li><code>status</code>: The HTTP status code</li>\n<li><code>detail</code>: A human-readable explanation specific to this occurrence of the problem</li>\n<li><code>instance</code>: A URI reference that identifies the specific occurrence of the problem</li>\n</ul>\n<p><a href=\"https://www.rfc-editor.org/rfc/rfc9457\">RFC 9457</a>, which replaces <a href=\"https://www.rfc-editor.org/rfc/rfc7807\">RFC 7807</a>,\nintroduces improvements such as clarifying the use of the type field and providing guidelines for extending <strong>Problem Details</strong>.</p>\n<p>Here's an example Problem Details response:</p>\n<pre><code class=\"language-json\">Content-Type: application/problem+json\n\n{\n  &quot;type&quot;: &quot;https://tools.ietf.org/html/rfc9110#section-15.5.5&quot;,\n  &quot;title&quot;: &quot;Not Found&quot;,\n  &quot;status&quot;: 404,\n  &quot;detail&quot;: &quot;The habit with the specified identifier was not found&quot;,\n  &quot;instance&quot;: &quot;PUT /api/habits/aadcad3f-8dc8-443d-be44-3d99893ba18a&quot;\n}\n</code></pre>\n<h2>Implementing Problem Details</h2>\n<p>Let's see how to implement Problem Details in ASP.NET Core.\nWe want to return a Problem Details response for unhandled exceptions.\nBy calling <code>AddProblemDetails</code>, we're configuring the application to use the Problem Details format for failed requests.\nWith <code>UseExceptionHandler</code>, we introduce an exception handling middleware to the request pipeline.\nBy adding <code>UseStatusCodePages</code>, we're introducing a middleware that will convert error responses with an empty body to a Problem Details response.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\n// Adds services for using Problem Details format\nbuilder.Services.AddProblemDetails();\n\nvar app = builder.Build();\n\n// Converts unhandled exceptions into Problem Details responses\napp.UseExceptionHandler();\n\n// Returns the Problem Details response for (empty) non-successful responses\napp.UseStatusCodePages();\n\napp.Run();\n</code></pre>\n<p>When we encounter an unhandled exception, it will be translated to a Problem Details response:</p>\n<pre><code class=\"language-json\">Content-Type: application/problem+json\n\n{\n  &quot;type&quot;: &quot;https://tools.ietf.org/html/rfc9110#section-15.6.1&quot;,\n  &quot;title&quot;: &quot;An error occurred while processing your request.&quot;,\n  &quot;status&quot;: 500\n}\n</code></pre>\n<p>Now, let's explore how we can customize this response.</p>\n<h2>Global Error Handling</h2>\n<p>We have a few options for <a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\">implementing global error handling</a>.\nThe most popular approach is creating a custom exception handling middleware.\nYou wrap the API request in a <code>try-catch</code> statement and return a response based on any caught exception.</p>\n<p>With .NET 8, we can use the <code>IExceptionHandler</code> that runs in the built-in exception handling middleware.\nThis handler allows you to tailor the Problem Details response for specific exceptions.\nReturning <code>true</code> from the <code>TryHandleAsync</code> method short-circuits the pipeline and returns the API response.\nIf we return <code>false</code>, the next handler in the chain attempts to handle the exception.</p>\n<p>We can map different exception types to appropriate HTTP status codes, providing more precise error information to API consumers.</p>\n<p>Here's an example <code>CustomExceptionHandler</code> implementation:</p>\n<pre><code class=\"language-csharp\">internal sealed class CustomExceptionHandler : IExceptionHandler\n{\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        int status = exception switch\n        {\n            ArgumentException =&gt; StatusCodes.Status400BadRequest,\n            _ =&gt; StatusCodes.Status500InternalServerError\n        };\n        httpContext.Response.StatusCode = status;\n\n        var problemDetails = new ProblemDetails\n        {\n            Status = status,\n            Title = &quot;An error occurred&quot;,\n            Type = exception.GetType().Name,\n            Detail = exception.Message\n        };\n\n        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);\n\n        return true;\n    }\n}\n\n// In Program.cs\nbuilder.Services.AddExceptionHandler&lt;CustomExceptionHandler&gt;();\n</code></pre>\n<h2>Using The ProblemDetailsService</h2>\n<p>Calling <code>AddProblemDetails</code> registers a default implementation of the <code>IProblemDetailsService</code>.\nThe <code>IProblemDetailsService</code> will set the response status code based on the <code>ProblemDetails.Status</code>.</p>\n<p>Here's how we can use it in the <code>CustomExceptionHandler</code>:</p>\n<pre><code class=\"language-csharp\">public class CustomExceptionHandler(IProblemDetailsService problemDetailsService) : IExceptionHandler\n{\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        var problemDetails = new ProblemDetails\n        {\n            Status = exception switch\n            {\n                ArgumentException =&gt; StatusCodes.Status400BadRequest,\n                _ =&gt; StatusCodes.Status500InternalServerError\n            },\n            Title = &quot;An error occurred&quot;,\n            Type = exception.GetType().Name,\n            Detail = exception.Message\n        };\n\n        return await problemDetailsService.TryWriteAsync(new ProblemDetailsContext\n        {\n            Exception = exception,\n            HttpContext = httpContext,\n            ProblemDetails = problemDetails\n        });\n    }\n}\n</code></pre>\n<p>This approach seems very similar to the previous one, where we wrote to the response body.\nHowever, using the <code>IProblemDetailsService</code> gives an easy way to customize all Problem Details responses.</p>\n<p>We can return Problem Details in controllers using the <code>Problem</code> method, or <code>Results.Problem</code> in <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal APIs</strong></a>.\nThese methods respect the configured Problem Details customizations (more on this in the next section).</p>\n<pre><code class=\"language-csharp\">IdentityUser identityUser = new() { UserName = registerUserDto.UserName, Email = registerUserDto.Email };\nIdentityResult result = await userManager.CreateAsync(identityUser, registerUserDto.Password);\n\nif (!result.Succeeded)\n{\n    // return Results.Problem - Minimal APIs\n    return Problem(\n        type: &quot;Bad Request&quot;,\n        title: &quot;Identity failure&quot;,\n        detail: result.Errors.First().Description,\n        statusCode: StatusCodes.Status400BadRequest);\n}\n</code></pre>\n<h2>Customizing Problem Details</h2>\n<p>We can pass a delegate to the <code>AddProblemDetails</code> method to set the <code>CustomizeProblemDetails</code>.\nYou can use this to add extra information to all Problem Details responses.</p>\n<p>This is an excellent place for solving cross-cutting concerns, like setting the <code>instance</code> value and adding diagnostics information.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddProblemDetails(options =&gt;\n{\n    options.CustomizeProblemDetails = context =&gt;\n    {\n        context.ProblemDetails.Instance =\n            $&quot;{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}&quot;;\n\n        context.ProblemDetails.Extensions.TryAdd(&quot;requestId&quot;, context.HttpContext.TraceIdentifier);\n\n        Activity? activity = context.HttpContext.Features.Get&lt;IHttpActivityFeature&gt;()?.Activity;\n        context.ProblemDetails.Extensions.TryAdd(&quot;traceId&quot;, activity?.Id);\n    };\n});\n</code></pre>\n<p>This customization adds the request path, a request ID, and a trace ID to every Problem Details response, enhancing debuggability and traceability of errors.</p>\n<pre><code class=\"language-json\">Content-Type: application/problem+json\n\n{\n  &quot;type&quot;: &quot;https://tools.ietf.org/html/rfc9110#section-15.5.5&quot;,\n  &quot;title&quot;: &quot;Not Found&quot;,\n  &quot;status&quot;: 404,\n  &quot;instance&quot;: &quot;PUT /api/habits/aadcad3f-8dc8-443d-be44-3d99893ba18a&quot;,\n  &quot;traceId&quot;: &quot;00-63d4af1807586b0d98901ae47944192d-9a8635facb90bf76-01&quot;,\n  &quot;requestId&quot;: &quot;0HN7C8PRNMGIA:00000001&quot;\n}\n</code></pre>\n<p>You can use the <code>traceId</code> to find the distributed traces and logs in a monitoring system like Seq.</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_112/seq_tracing.png\" alt=\"Seq user interface showing a distributed trace with the same trace identifier as the problem details response.\">\n</div>\n<h2>Handling Specific Exceptions (Status Codes)</h2>\n<p>.NET 9 introduces a simpler way to map exceptions to status codes.\nGreat news for fans of throwing exceptions.\nYou can use the <code>StatusCodeSelector</code> to define the mappings.\nThis makes it easier to maintain consistent error responses across your API.</p>\n<pre><code class=\"language-csharp\">app.UseExceptionHandler(new ExceptionHandlerOptions\n{\n    StatusCodeSelector = ex =&gt; ex switch\n    {\n        ArgumentException =&gt; StatusCodes.Status400BadRequest,\n        NotFoundException =&gt; StatusCodes.Status404NotFound,\n        _ =&gt; StatusCodes.Status500InternalServerError\n    }\n});\n</code></pre>\n<p>If you use this together with an <code>IExceptionHandler</code> that sets the <code>StatusCode</code>, then the <code>StatusCodeSelector</code> is ignored.</p>\n<h2>Takeaway</h2>\n<p>Implementing Problem Details in your ASP.NET Core APIs is more than just a best practice -\nit's a standard for improving the developer experience of your API consumers.\nBy providing consistent, detailed, and well-structured error responses, you make it easier for clients to understand and handle error scenarios gracefully.</p>\n<p>As you implement these practices in your own projects, you'll discover even more ways to tailor Problem Details to your specific needs.\nI shared what worked well for my use cases.</p>\n<p>Problem Details is just one of the best practices covered in my <a href=\"https://milanjovanovic.tech/pragmatic-rest-apis\"><strong>REST APIs course</strong></a>.\nCheck it out if you're looking for a comprehensive guide.</p>\n<p>Good luck out there, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/problem-details-for-aspnetcore-apis",
            "title": "Problem Details for ASP.NET Core APIs",
            "summary": "Problem Details gives your ASP.NET Core APIs consistent, machine-readable error responses that comply with RFC 9457.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_112.png",
            "date_modified": "2024-10-19T00:00:00.000Z",
            "date_published": "2024-10-19T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/scaling-the-outbox-pattern",
            "content_html": "<p>A covering index on the unprocessed-message query, batched publishes, one batched <code>UPDATE</code>, and five parallel workers took my Outbox processor from 1,350 to about 32,500 messages per second.\nThat is over 2.8 billion messages per day.\nOrdering is no longer guaranteed, and consumers still have to be idempotent.</p>\n<p>In last week's newsletter, I talked about <a href=\"https://milanjovanovic.tech/blog/implementing-the-outbox-pattern\">implementing the Outbox pattern</a>.\nIt's a crucial tool for reliable distributed messaging.\nBut implementing it is just the first step.</p>\n<p>The real challenge? Scaling it to handle massive message volumes.</p>\n<p>Today, we're taking it up a notch.\nWe'll start with a basic Outbox processor and transform it into a high-performance engine capable of handling over 2 billion messages daily.</p>\n<p>Let's dive in!</p>\n<h2>Starting Point</h2>\n<p>This is our starting point.\nWe have an <code>OutboxProcessor</code> that polls for unprocessed messages and publishes them to a queue.\nThe first few things we can tweak are the <strong>frequency</strong> and <strong>batch size</strong>.</p>\n<pre><code class=\"language-csharp\">internal sealed class OutboxProcessor(NpgsqlDataSource dataSource, IPublishEndpoint publishEndpoint)\n{\n    private const int BatchSize = 1000;\n\n    public async Task&lt;int&gt; Execute(CancellationToken cancellationToken = default)\n    {\n        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);\n        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);\n\n        var messages = await connection.QueryAsync&lt;OutboxMessage&gt;(\n            @&quot;&quot;&quot;\n            SELECT *\n            FROM outbox_messages\n            WHERE processed_on_utc IS NULL\n            ORDER BY occurred_on_utc LIMIT @BatchSize\n            &quot;&quot;&quot;,\n            new { BatchSize },\n            transaction: transaction);\n\n        foreach (var message in messages)\n        {\n            try\n            {\n                var messageType = Messaging.Contracts.AssemblyReference.Assembly.GetType(message.Type);\n                var deserializedMessage = JsonSerializer.Deserialize(message.Content, messageType);\n\n                await publishEndpoint.Publish(deserializedMessage, messageType, cancellationToken);\n\n                await connection.ExecuteAsync(\n                    @&quot;&quot;&quot;\n                    UPDATE outbox_messages\n                    SET processed_on_utc = @ProcessedOnUtc\n                    WHERE id = @Id\n                    &quot;&quot;&quot;,\n                    new { ProcessedOnUtc = DateTime.UtcNow, message.Id },\n                    transaction: transaction);\n            }\n            catch (Exception ex)\n            {\n                await connection.ExecuteAsync(\n                    @&quot;&quot;&quot;\n                    UPDATE outbox_messages\n                    SET processed_on_utc = @ProcessedOnUtc, error = @Error\n                    WHERE id = @Id\n                    &quot;&quot;&quot;,\n                    new { ProcessedOnUtc = DateTime.UtcNow, Error = ex.ToString(), message.Id },\n                    transaction: transaction);\n            }\n        }\n\n        await transaction.CommitAsync(cancellationToken);\n\n        return messages.Count;\n    }\n}\n</code></pre>\n<p>Let's assume that we run the <code>OutboxProcessor</code> continuously.\nI increased that batch size to <code>1000</code>.</p>\n<p>How many messages are we able to process?</p>\n<p>I'll run the Outbox processing for 1 minute and count how many messages were processed.</p>\n<p>The baseline implementation processed <strong>81,000</strong> messages in one minute or <strong>1,350 MPS</strong> (messages per second).</p>\n<p>Not bad, but let's see how much we can improve this.</p>\n<h2>Measuring Each Step</h2>\n<p>You can't improve what you can't measure. Right?\nSo, I'll use a <code>Stopwatch</code> to measure the total execution time and the time each step takes.</p>\n<p>Notice that I also split the publish and update steps.\nIt's so I can measure the time for publishing and updating separately.\nThis will be important later because I want to optimize each step separately.</p>\n<p>With the baseline implementation, here are the execution times for each step:</p>\n<ul>\n<li>Query time: ~70ms</li>\n<li>Publish time: ~320ms</li>\n<li>Update time: ~300ms</li>\n</ul>\n<pre><code class=\"language-csharp\">internal sealed class OutboxProcessor(\n    NpgsqlDataSource dataSource,\n    IPublishEndpoint publishEndpoint,\n    ILogger&lt;OutboxProcessor&gt; logger)\n{\n    private const int BatchSize = 1000;\n\n    public async Task&lt;int&gt; Execute(CancellationToken cancellationToken = default)\n    {\n        var totalStopwatch = Stopwatch.StartNew();\n        var stepStopwatch = new Stopwatch();\n\n        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);\n        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);\n\n        stepStopwatch.Restart();\n        var messages = (await connection.QueryAsync&lt;OutboxMessage&gt;(\n            @&quot;&quot;&quot;\n            SELECT *\n            FROM outbox_messages\n            WHERE processed_on_utc IS NULL\n            ORDER BY occurred_on_utc LIMIT @BatchSize\n            &quot;&quot;&quot;,\n            new { BatchSize },\n            transaction: transaction)).AsList();\n        var queryTime = stepStopwatch.ElapsedMilliseconds;\n\n        var updateQueue = new ConcurrentQueue&lt;OutboxUpdate&gt;();\n\n        stepStopwatch.Restart();\n        foreach (var message in messages)\n        {\n            try\n            {\n                var messageType = Messaging.Contracts.AssemblyReference.Assembly.GetType(message.Type);\n                var deserializedMessage = JsonSerializer.Deserialize(message.Content, messageType);\n\n                await publishEndpoint.Publish(deserializedMessage, messageType, cancellationToken);\n\n                updateQueue.Enqueue(new OutboxUpdate\n                {\n                    Id = message.Id,\n                    ProcessedOnUtc = DateTime.UtcNow\n                });\n            }\n            catch (Exception ex)\n            {\n                updateQueue.Enqueue(new OutboxUpdate\n                {\n                    Id = message.Id,\n                    ProcessedOnUtc = DateTime.UtcNow,\n                    Error = ex.ToString()\n                });\n            }\n        }\n        var publishTime = stepStopwatch.ElapsedMilliseconds;\n\n        stepStopwatch.Restart();\n        foreach (var outboxUpdate in updateQueue)\n        {\n            await connection.ExecuteAsync(\n                @&quot;&quot;&quot;\n                UPDATE outbox_messages\n                SET processed_on_utc = @ProcessedOnUtc, error = @Error\n                WHERE id = @Id\n                &quot;&quot;&quot;,\n                outboxUpdate,\n                transaction: transaction);\n        }\n        var updateTime = stepStopwatch.ElapsedMilliseconds;\n\n        await transaction.CommitAsync(cancellationToken);\n\n        totalStopwatch.Stop();\n        var totalTime = totalStopwatch.ElapsedMilliseconds;\n\n        OutboxLoggers.Processing(logger, totalTime, queryTime, publishTime, updateTime, messages.Count);\n\n        return messages.Count;\n    }\n\n    private struct OutboxUpdate\n    {\n        public Guid Id { get; init; }\n        public DateTime ProcessedOnUtc { get; init; }\n        public string? Error { get; init; }\n    }\n}\n</code></pre>\n<p>Now, onto the fun part!</p>\n<h2>Optimizing Read Queries</h2>\n<p>The first thing I want to optimize is the query for fetching unprocessed messages.\nPerforming a <code>SELECT *</code> query will have an impact if we don't need all the columns (hint: we don't).</p>\n<p>Here's the current SQL query:</p>\n<pre><code class=\"language-sql\">SELECT *\nFROM outbox_messages\nWHERE processed_on_utc IS NULL\nORDER BY occurred_on_utc LIMIT @BatchSize\n</code></pre>\n<p>We can modify the query to return only the columns we need.\nThis will save us some bandwidth but will not significantly improve performance.</p>\n<pre><code class=\"language-sql\">SELECT id AS Id, type AS Type, content as Content\nFROM outbox_messages\nWHERE processed_on_utc IS NULL\nORDER BY occurred_on_utc LIMIT @BatchSize\n</code></pre>\n<p>Let's examine the execution plan for this query.\nYou'll see it's performing a table scan.\nI'm running this on PostgreSQL, and here's what I get from <code>EXPLAIN ANALYZE</code>:</p>\n<pre><code>Limit  (cost=86169.40..86286.08 rows=1000 width=129) (actual time=122.744..124.234 rows=1000 loops=1)\n  -&gt;  Gather Merge  (cost=86169.40..245080.50 rows=1362000 width=129) (actual time=122.743..124.198 rows=1000 loops=1)\n        Workers Planned: 2\n        Workers Launched: 2\n        -&gt;  Sort  (cost=85169.38..86871.88 rows=681000 width=129) (actual time=121.478..121.492 rows=607 loops=3)\n              Sort Key: occurred_on_utc\n              Sort Method: top-N heapsort  Memory: 306kB\n              Worker 0:  Sort Method: top-N heapsort  Memory: 306kB\n              Worker 1:  Sort Method: top-N heapsort  Memory: 306kB\n              -&gt;  Parallel Seq Scan on outbox_messages  (cost=0.00..47830.88 rows=681000 width=129) (actual time=0.016..67.481 rows=666667 loops=3)\n                    Filter: (processed_on_utc IS NULL)\nPlanning Time: 0.051 ms\nExecution Time: 124.298 ms\n</code></pre>\n<p>Now, I'll create an index that &quot;covers&quot; the query for fetching unprocessed messages.\nA covered index contains all the columns needed to satisfy a query without accessing the table itself.</p>\n<p>The index will be on the <code>occurred_on_utc</code> and <code>processed_on_utc</code> columns.\nIt will include the <code>id</code>, <code>type</code>, and <code>content</code> columns.\nLastly, we'll apply a filter to index unprocessed messages only.</p>\n<pre><code class=\"language-sql\">CREATE INDEX IF NOT EXISTS idx_outbox_messages_unprocessed\nON public.outbox_messages (occurred_on_utc, processed_on_utc)\nINCLUDE (id, type, content)\nWHERE processed_on_utc IS NULL\n</code></pre>\n<p>Let me explain the reasoning behind each decision:</p>\n<ul>\n<li>Indexing the <code>occurred_on_utc</code> will store the entries in the index in ascending order.\nThis matches the <code>ORDER BY occurred_on_utc</code> statement in the query.\nThis means the query can scan the index without sorting the results.\nThe results are already in the correct sort order.</li>\n<li>Including the columns we select in the index allows us to return them from the index entry.\nThis avoids reading the values from the table rows.</li>\n<li>Filtering for unprocessed messages in the index satisfies the <code>WHERE processed_on_utc IS NULL</code> statement.</li>\n</ul>\n<p><strong>Caveat</strong>: PostgreSQL has a maximum index row size of <strong>2712B</strong> (don't ask how I know).\nThe columns in the <code>INCLUDE</code> list are also part of the index row (B-tree tuple).\nThe <code>content</code> column contains the serialized JSON message, so it's the most likely culprit to make us exceed this limit.\nThere's no way around it, so my advice is to keep your messages as small as possible.\nYou could exclude this column from the <code>INCLUDE</code> list for a minor performance hit.</p>\n<p>Here's the updated execution plan after creating this index:</p>\n<pre><code>Limit  (cost=0.43..102.82 rows=1000 width=129) (actual time=0.016..0.160 rows=1000 loops=1)\n  -&gt;  Index Only Scan using idx_outbox_messages_unprocessed on outbox_messages  (cost=0.43..204777.36 rows=2000000 width=129) (actual time=0.015..0.125 rows=1000 loops=1)\n        Heap Fetches: 0\nPlanning Time: 0.059 ms\nExecution Time: 0.189 ms\n</code></pre>\n<p>Because we have a covered index, the execution plan only contains an <code>Index Only Scan</code> and <code>Limit</code> operation.\nThere's no filtering or sorting that needs to happen, which is why we see a massive performance improvement.</p>\n<p>What's the performance impact on the query time?</p>\n<ul>\n<li>Query time: 70ms → 1ms <strong>(-98.5%)</strong></li>\n</ul>\n<h2>Optimizing Message Publishing</h2>\n<p>The next thing we can optimize is how we're publishing messages to the queue.\nI'm using the <code>IPublishEndpoint</code> from <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>MassTransit</strong></a> to publish to RabbitMQ.</p>\n<p>To be more precise here, we're publishing to an exchange.\nThe exchange will then route the message to the appropriate queue.</p>\n<p>But how can we optimize this?</p>\n<p>A micro-optimization we can do is introduce a cache for the message types used in serialization.\nPerforming reflection constantly for every message type is expensive, so we'll do the reflection once, and store the result.</p>\n<pre><code class=\"language-csharp\">var messageType = Messaging.Contracts.AssemblyReference.Assembly.GetType(message.Type);\n</code></pre>\n<p>The cache can be a <code>ConcurrentDictionary</code>, and we'll use <code>GetOrAdd</code> to retrieve the cached types.</p>\n<p>I'll extract this piece of code to the <code>GetOrAddMessageType</code> helper method:</p>\n<pre><code class=\"language-csharp\">private static readonly ConcurrentDictionary&lt;string, Type&gt; TypeCache = new();\n\nprivate static Type GetOrAddMessageType(string typeName)\n{\n    return TypeCache.GetOrAdd(\n        typeName,\n        name =&gt; Messaging.Contracts.AssemblyReference.Assembly.GetType(name));\n}\n</code></pre>\n<p>This is what our message publishing step looks like.\nThe biggest problem is we're waiting for the <code>Publish</code> to complete by awaiting it.\nThe <code>Publish</code> takes some time because it's waiting for confirmation from the message broker.\nWe're doing this in a loop, which makes it even less efficient.</p>\n<pre><code class=\"language-csharp\">var updateQueue = new ConcurrentQueue&lt;OutboxUpdate&gt;();\n\nforeach (var message in messages)\n{\n    try\n    {\n        var messageType = Messaging.Contracts.AssemblyReference.Assembly.GetType(message.Type);\n        var deserializedMessage = JsonSerializer.Deserialize(message.Content, messageType);\n\n        // We're waiting for the message broker confirmation here.\n        await publishEndpoint.Publish(deserializedMessage, messageType, cancellationToken);\n\n        updateQueue.Enqueue(new OutboxUpdate\n        {\n            Id = message.Id,\n            ProcessedOnUtc = DateTime.UtcNow\n        });\n    }\n    catch (Exception ex)\n    {\n        updateQueue.Enqueue(new OutboxUpdate\n        {\n            Id = message.Id,\n            ProcessedOnUtc = DateTime.UtcNow,\n            Error = ex.ToString()\n        });\n    }\n}\n</code></pre>\n<p>We can improve this by publishing the messages in a batch.\nIn fact, the <code>IPublishEndpoint</code> has a <code>PublishBatch</code> extension method.\nIf we peek inside, here's what we'll find:</p>\n<pre><code class=\"language-csharp\">// MassTransit implementation\npublic static Task PublishBatch(\n    this IPublishEndpoint endpoint,\n    IEnumerable&lt;object&gt; messages,\n    CancellationToken cancellationToken = default)\n{\n    return Task.WhenAll(messages.Select(x =&gt; endpoint.Publish(x, cancellationToken)));\n}\n</code></pre>\n<p>So we can transform the collection of messages into a list of publishing tasks that we can await using <code>Task.WhenAll</code>.</p>\n<pre><code class=\"language-csharp\">var updateQueue = new ConcurrentQueue&lt;OutboxUpdate&gt;();\n\nvar publishTasks = messages\n    .Select(message =&gt; PublishMessage(message, updateQueue, publishEndpoint, cancellationToken))\n    .ToList();\n\nawait Task.WhenAll(publishTasks);\n\n// I extracted the message publishing into a separate method for readability.\nprivate static async Task PublishMessage(\n    OutboxMessage message,\n    ConcurrentQueue&lt;OutboxUpdate&gt; updateQueue,\n    IPublishEndpoint publishEndpoint,\n    CancellationToken cancellationToken)\n{\n    try\n    {\n        var messageType = GetOrAddMessageType(message.Type);\n        var deserializedMessage = JsonSerializer.Deserialize(message.Content, messageType);\n\n        await publishEndpoint.Publish(deserializedMessage, messageType, cancellationToken);\n\n        updateQueue.Enqueue(new OutboxUpdate\n        {\n            Id = message.Id,\n            ProcessedOnUtc = DateTime.UtcNow\n        });\n    }\n    catch (Exception ex)\n    {\n        updateQueue.Enqueue(new OutboxUpdate\n        {\n            Id = message.Id,\n            ProcessedOnUtc = DateTime.UtcNow,\n            Error = ex.ToString()\n        });\n    }\n}\n</code></pre>\n<p>What's the improvement for the message publishing step?</p>\n<ul>\n<li>Publish time: 320ms → 289ms <strong>(-9.8%)</strong></li>\n</ul>\n<p>As you can see, it's not significantly faster.\nBut this is needed for us to benefit from other optimizations I have in store.</p>\n<h2>Optimizing Update Queries</h2>\n<p>The next step in our optimization journey is addressing the query updating the processed Outbox messages.</p>\n<p>The current implementation is inefficient because we send one query to the database for each Outbox message.</p>\n<pre><code class=\"language-csharp\">foreach (var outboxUpdate in updateQueue)\n{\n    await connection.ExecuteAsync(\n        @&quot;&quot;&quot;\n        UPDATE outbox_messages\n        SET processed_on_utc = @ProcessedOnUtc, error = @Error\n        WHERE id = @Id\n        &quot;&quot;&quot;,\n        outboxUpdate,\n        transaction: transaction);\n}\n</code></pre>\n<p>If you didn't get the memo by now, batching is the name of the game.\nWe want a way to send one large <code>UPDATE</code> query to the database.</p>\n<p>We have to construct the SQL for this batch query manually.\nWe'll use the <code>DynamicParameters</code> type from <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a> to provide all the parameters.</p>\n<pre><code class=\"language-csharp\">var updateSql =\n    @&quot;&quot;&quot;\n    UPDATE outbox_messages\n    SET processed_on_utc = v.processed_on_utc,\n        error = v.error\n    FROM (VALUES\n        {0}\n    ) AS v(id, processed_on_utc, error)\n    WHERE outbox_messages.id = v.id::uuid\n    &quot;&quot;&quot;;\n\nvar updates = updateQueue.ToList();\nvar paramNames = string.Join(&quot;,&quot;, updates.Select((_, i) =&gt; $&quot;(@Id{i}, @ProcessedOn{i}, @Error{i})&quot;));\n\nvar formattedSql = string.Format(updateSql, paramNames);\n\nvar parameters = new DynamicParameters();\n\nfor (int i = 0; i &lt; updates.Count; i++)\n{\n    parameters.Add($&quot;Id{i}&quot;, updates[i].Id.ToString());\n    parameters.Add($&quot;ProcessedOn{i}&quot;, updates[i].ProcessedOnUtc);\n    parameters.Add($&quot;Error{i}&quot;, updates[i].Error);\n}\n\nawait connection.ExecuteAsync(formattedSql, parameters, transaction: transaction);\n</code></pre>\n<p>This will produce a SQL query that looks something like this:</p>\n<pre><code class=\"language-sql\">UPDATE outbox_messages\nSET processed_on_utc = v.processed_on_utc,\n    error = v.error\nFROM (VALUES\n    (@Id0, @ProcessedOn0, @Error0),\n    (@Id1, @ProcessedOn1, @Error1),\n    (@Id2, @ProcessedOn2, @Error2),\n    -- A few hundred rows in beteween\n    (@Id999, @ProcessedOn999, @Error999)\n) AS v(id, processed_on_utc, error)\nWHERE outbox_messages.id = v.id::uuid\n</code></pre>\n<p>Instead of sending one update query per message, we can send one query to update all messages.</p>\n<p>This will obviously give us a noticeable performance benefit:</p>\n<ul>\n<li>Update time: 300ms → 52ms <strong>(-82.6%)</strong></li>\n</ul>\n<h2>How Far Did We Get?</h2>\n<p>Let's test out the performance improvement with the current optimizations.\nThe changes we made so far focus on improving the speed of the <code>OutboxProcessor</code>.</p>\n<p>Here are the rough numbers I'm seeing for the individual steps:</p>\n<ul>\n<li>Query time: ~<strong>1ms</strong></li>\n<li>Publish time: ~<strong>289ms</strong></li>\n<li>Update time: ~<strong>52ms</strong></li>\n</ul>\n<p>I'll run the Outbox processing for 1 minute and count the number of processed messages.</p>\n<p>The optimized implementation processed <strong>162,000</strong> messages in one minute or <strong>2,700 MPS</strong>.</p>\n<p>For reference, this allows us to process more than 230 million messages per day.</p>\n<p>But we're just getting started.</p>\n<h2>Parallel Outbox Processing</h2>\n<p>If we want to take this further, we have to scale out the <code>OutboxProcessor</code>.\nThe problem we could face here is processing the same message more than once.\nSo, we need to implement some form of locking on the current batch of messages.</p>\n<p>PostgreSQL has a convenient <code>FOR UPDATE</code> statement that we can use here.\nIt will lock the selected rows for the duration of the current transaction.\nHowever, we must add the <code>SKIP LOCKED</code> statement to allow other queries to skip the locked rows.\nOtherwise, any other query will be blocked until the current transaction is completed.</p>\n<p>Here's the updated query:</p>\n<pre><code class=\"language-sql\">SELECT id AS Id, type AS Type, content as Content\nFROM outbox_messages\nWHERE processed_on_utc IS NULL\nORDER BY occurred_on_utc LIMIT @BatchSize\nFOR UPDATE SKIP LOCKED\n</code></pre>\n<p>To scale out the <code>OutboxProcessor</code>, we simply run multiple instances of the background job.</p>\n<p>I'll simulate this using <code>Parallel.ForEachAsync</code>, where I can control the <code>MaxDegreeOfParallelism</code>.</p>\n<pre><code class=\"language-csharp\">var parallelOptions = new ParallelOptions\n{\n    MaxDegreeOfParallelism = _maxParallelism,\n    CancellationToken = cancellationToken\n};\n\nawait Parallel.ForEachAsync(\n    Enumerable.Range(0, _maxParallelism),\n    parallelOptions,\n    async (_, token) =&gt;\n    {\n        await ProcessOutboxMessages(token);\n    });\n</code></pre>\n<p>We can process <strong>179,000</strong> messages in one minute or <strong>2,983 MPS</strong> with five (5) workers.</p>\n<p>I thought this was supposed to be <em>much</em> faster. What gives?</p>\n<p>Without parallel processing, we were able to get ~2,700 MPS.</p>\n<p>A new <strong>bottleneck</strong> appears: publishing the messages in batches.</p>\n<p>The publish time went from ~289ms to ~1,540ms.</p>\n<p>Interestingly, if you multiply the base publish time (for one worker) by the number of workers, you roughly get to the new publish time.</p>\n<p>We're wasting a lot of time waiting for the acknowledgment from the message broker.</p>\n<p>How can we fix this?</p>\n<h2>Batching Message Publishing</h2>\n<div className=\"note-panel\">\n  **Note**: `ConfigureBatchPublish` was deprecated with `MassTransit.RabbitMQ`\n  v8.3.2. This version uses the new `RabbitMQ.Client` v7, which was rewritten to\n  use the TPL and async/await. You will see a similar performance improvement\n  just from upgrading to this version.\n</div>\n<p>RabbitMQ supports publishing messages in batches.\nWe can enable this feature when configuring MassTransit by calling the <code>ConfigureBatchPublish</code> method.\nMassTransit will buffer messages before sending them to RabbitMQ, to increase throughput.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(x =&gt;\n{\n    x.UsingRabbitMq((context, cfg) =&gt;\n    {\n        cfg.Host(builder.Configuration.GetConnectionString(&quot;Queue&quot;), hostCfg =&gt;\n        {\n            hostCfg.ConfigureBatchPublish(batch =&gt;\n            {\n                batch.Enabled = true;\n            });\n        });\n\n        cfg.ConfigureEndpoints(context);\n    });\n});\n</code></pre>\n<p>With only this small change, let's rerun our test with five workers.</p>\n<p>This time around, we're able to process <strong>1,956,000</strong> messages in one minute.</p>\n<p>Which gives us a blazing ~<strong>32,500 MPS</strong>.</p>\n<p>This is more than 2.8 billion processed messages per day.</p>\n<p>I could call it a day here, but there's one more thing I want to show you.</p>\n<h2>Turning Off Publisher Confirmation (Dangerous)</h2>\n<p>One more thing you <em>can</em> do (<strong>which I don't recommend</strong>) is turn off publisher confirmation.\nThis means that calling <code>Publish</code> won't wait until the message is confirmed by the broker (ack'd).\nIt could lead to <strong>reliability issues</strong> and potentially <strong>losing messages</strong>.</p>\n<p>That being said, I did manage to get ~37,000 MPS with publisher confirmation turned off.</p>\n<pre><code class=\"language-csharp\">cfg.Host(builder.Configuration.GetConnectionString(&quot;Queue&quot;), hostCfg =&gt;\n{\n    hostCfg.PublisherConfirmation = false; // Dangerous. I don't recommend it.\n    hostCfg.ConfigureBatchPublish(batch =&gt;\n    {\n        batch.Enabled = true;\n    });\n});\n</code></pre>\n<h2>Key Considerations for Scaling</h2>\n<p>While we've achieved impressive throughput, consider these factors when implementing these techniques in a real-world system:</p>\n<ol>\n<li>\n<p><strong>Consumer Capacity</strong>:\nCan your consumers keep up?\nBoosting producer throughput without matching consumer capacity can create backlogs.\nConsider the entire pipeline when scaling.</p>\n</li>\n<li>\n<p><strong>Delivery Guarantees</strong>:\nOur optimizations maintain at-least-once delivery.\nDesign consumers to be <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>idempotent</strong></a> to handle occasional duplicate messages.</p>\n</li>\n<li>\n<p><strong>Message Ordering</strong>:\nParallel processing with <code>FOR UPDATE SKIP LOCKED</code> may cause out-of-order messages.\nFor strict ordering, consider the <strong>Inbox pattern</strong> on the consumer side to buffer messages.\nAn Inbox allows us to process messages in the correct order, even if they arrive out of sequence.</p>\n</li>\n<li>\n<p><strong>Reliability vs. Performance Trade-offs</strong>:\nTurning off publisher confirmation increases speed but risks message loss.\nWeigh performance against reliability based on your specific needs.</p>\n</li>\n</ol>\n<p>By addressing these factors, you'll create a high-performance Outbox processor that integrates smoothly with your system architecture.</p>\n<h2>Summary</h2>\n<p>We've come a long way from our initial Outbox processor.\nHere's what we accomplished:</p>\n<ol>\n<li>Optimized database queries with smart indexing</li>\n<li>Improved message publishing with batching</li>\n<li>Streamlined database updates with batching</li>\n<li>Scaled out Outbox processing with parallel workers</li>\n<li>Leveraged RabbitMQ's batch publishing feature</li>\n</ol>\n<p>The result?\nWe boosted processing from 1,350 messages per second to an impressive <strong>32,500 MPS</strong>.\nThat's over 2.8 billion messages per day!</p>\n<p>Scaling isn't just about raw speed - it's about identifying and addressing bottlenecks at each step.\nBy measuring, optimizing, and rethinking our approach, we achieved massive performance gains.</p>\n<p>That's all for today. Hope this was helpful.</p>\n<p><strong>P.S.</strong> You can find the <a href=\"https://github.com/m-jovanovic/outbox-scaling\">source code</a> here.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/scaling-the-outbox-pattern",
            "title": "Scaling the Outbox Pattern (2B+ messages per day)",
            "summary": "Learn how to supercharge your Outbox pattern implementation, scaling to 32,500 messages per second.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_111.png",
            "date_modified": "2024-10-12T00:00:00.000Z",
            "date_published": "2024-10-12T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-the-outbox-pattern",
            "content_html": "<p>The Outbox pattern fixes the dual-write problem: saving data and publishing a message are two separate steps that can fail independently.\nYou write the message to an outbox table in the same transaction as the business data, and a background processor publishes it later.\nDelivery is at-least-once, so consumers must be idempotent.</p>\n<p>In distributed systems, we often face the challenge of keeping our database and external systems in sync.\nImagine saving an order to a database and then publishing a message to a message broker.\nIf either operation fails, your system ends up in an inconsistent state.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox pattern</strong></a> solves this problem by treating message publication as part of your database transaction.\nInstead of publishing messages directly, we save them to an Outbox table in our database, ensuring atomic operations.\nA separate process then reliably publishes these messages.</p>\n<p>In this newsletter, we'll dive into implementing this pattern in .NET, covering everything from setup to scaling.</p>\n<h2>Why Do We Need the Outbox Pattern?</h2>\n<p>The transactional Outbox pattern fixes a common problem in distributed systems.\nThis problem happens when you need to do two things at once: save data and communicate with an external component.</p>\n<p>Consider scenarios like sending order confirmation emails, notifying other systems about new client registrations,\nor updating inventory levels after an order is placed.\nEach of these involves a local data change coupled with an external communication or update.</p>\n<p>For example, imagine a microservice that needs to:</p>\n<ul>\n<li>Save a new order in its database</li>\n<li>Tell other systems about this new order</li>\n</ul>\n<p>If one of these steps fails, your system could end up in an inconsistent state.\nMaybe the order is saved, but no one else knows about it.\nOr everyone thinks there's a new order, but it's not actually in the database.</p>\n<p>Here's a <code>CreateOrderCommandHandler</code> without the Outbox pattern:</p>\n<pre><code class=\"language-csharp\">public class CreateOrderCommandHandler(\n    IOrderRepository orderRepository,\n    IProductInventoryChecker inventoryChecker,\n    IUnitOfWork unitOfWork,\n    IEventBus eventBus) : IRequestHandler&lt;CreateOrderCommand, OrderDto&gt;\n{\n    public async Task&lt;OrderDto&gt; Handle(CreateOrderCommand request, CancellationToken cancellationToken)\n    {\n        var order = new Order(request.CustomerId, request.ProductId, request.Quantity, inventoryChecker);\n\n        await orderRepository.AddAsync(order);\n\n        await unitOfWork.CommitAsync(cancellationToken);\n\n        // The database transaction is completed at this point.\n\n        await eventBus.Send(new OrderCreatedIntegrationEvent(order.Id));\n\n        return new OrderDto { Id = order.Id, Total = order.Total };\n    }\n}\n</code></pre>\n<p>This code has a potential consistency problem.\nAfter the database transaction is committed, two things could go wrong:</p>\n<ol>\n<li>\n<p>The application might crash right after the transaction is committed but before the event is sent.\nThe order would be created in the database, but other systems wouldn't know about it.</p>\n</li>\n<li>\n<p>The event bus might be down or unreachable when we try to send the event.\nThis would result in the order being created without notifying other systems.</p>\n</li>\n</ol>\n<p>The transactional Outbox pattern helps solve this problem by ensuring that the database update and event publication are treated as a single atomic operation.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_110/outbox_pattern.png\" alt=\"Flow diagram explaining how the Outbox Pattern works.\">\n<p>The sequence diagram illustrates how the Outbox pattern solves our consistency challenge.\nInstead of trying to save data and send a message as separate steps, we save both the order and an Outbox message in a <strong>single database transaction</strong>.\nThis is an all-or-nothing operation - we can't end up in an inconsistent state.</p>\n<p>A separate Outbox processor handles the actual message sending.\nIt continuously checks for unsent messages in the Outbox table and publishes them to the message queue.\nThe processor marks messages as sent after successful publishing, preventing duplicates.</p>\n<p>An important thing to realize here is that the Outbox pattern gives us <a href=\"https://www.cloudcomputingpatterns.org/at_least_once_delivery/\">at-least-once delivery</a>.\nThe Outbox message will be sent at least once, but it could also be sent multiple times in case of retries.\nThis means we have to make our message consumers idempotent.</p>\n<h2>Implementing the Outbox Pattern</h2>\n<p>First, let's create our Outbox table where we will store messages:</p>\n<pre><code class=\"language-sql\">CREATE TABLE outbox_messages (\n    id UUID PRIMARY KEY,\n    type VARCHAR(255) NOT NULL,\n    content JSONB NOT NULL,\n    occurred_on_utc TIMESTAMP WITH TIME ZONE NOT NULL,\n    processed_on_utc TIMESTAMP WITH TIME ZONE NULL,\n    error TEXT NULL\n);\n\n-- We can consider adding this index since we will be querying for unprocessed messages often\n-- and it will contain the rows in the correct sort order for our query.\nCREATE INDEX IF NOT EXISTS idx_outbox_messages_unprocessed\nON outbox_messages (occurred_on_utc, processed_on_utc)\nINCLUDE (id, type, content)\nWHERE processed_on_utc IS NULL;\n</code></pre>\n<p>I'll use PostgreSQL as the database for this example.\nNotice the <code>jsonb</code> type for the <code>content</code> column.\nIt allows for indexing and querying of the JSON data if needed in the future.</p>\n<p>Now, let's create a class to represent our Outbox entry:</p>\n<pre><code class=\"language-csharp\">public sealed class OutboxMessage\n{\n    public Guid Id { get; init; }\n    public string Type { get; init; }\n    public string Content { get; init; }\n    public DateTime OccurredOnUtc { get; init; }\n    public DateTime? ProcessedOnUtc { get; init; }\n    public string? Error { get; init; }\n}\n</code></pre>\n<p>Here's how we can add a message to the Outbox:</p>\n<pre><code class=\"language-csharp\">public async Task AddToOutbox&lt;T&gt;(T message, NpgsqlDataSource dataSource)\n{\n    var outboxMessage = new OutboxMessage\n    {\n        Id = Guid.NewGuid(),\n        OccurredOnUtc = DateTime.UtcNow,\n        Type = typeof(T).FullName, // We'll need this for deserialization\n        Content = JsonSerializer.Serialize(message)\n    };\n\n    await using var connection = await dataSource.OpenConnectionAsync();\n    await connection.ExecuteAsync(\n        @&quot;&quot;&quot;\n        INSERT INTO outbox_messages (id, occurred_on_utc, type, content)\n        VALUES (@Id, @OccurredOnUtc, @Type, @Content::jsonb)\n        &quot;&quot;&quot;,\n        outboxMessage);\n}\n</code></pre>\n<p>Here's the <code>CreateOrderCommandHandler</code> using the Outbox pattern:</p>\n<pre><code class=\"language-csharp\">public class CreateOrderCommandHandler(\n    IOrderRepository orderRepository,\n    IProductInventoryChecker inventoryChecker,\n    IUnitOfWork unitOfWork,\n    NpgsqlDataSource dataSource) : IRequestHandler&lt;CreateOrderCommand, OrderDto&gt;\n{\n    public async Task&lt;OrderDto&gt; Handle(CreateOrderCommand request, CancellationToken cancellationToken)\n    {\n        var order = new Order(request.CustomerId, request.ProductId, request.Quantity, inventoryChecker);\n\n        await orderRepository.AddAsync(order);\n\n        // We can add the Outbox message before committing the transaction,\n        // so both operations are part of the same transaction.\n        await AddToOutbox(new OrderCreatedIntegrationEvent(order.Id), dataSource);\n\n        await unitOfWork.CommitAsync(cancellationToken);\n\n        return new OrderDto { Id = order.Id, Total = order.Total };\n    }\n}\n</code></pre>\n<p>An elegant approach to implementing this is using <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>domain events</strong></a> to represent notifications.\nWhen something significant happens in the domain, we will raise a domain event.\nBefore completing the transaction, we can pick up all events and store them as Outbox messages.\nYou could do this from the unit of work or with an <a href=\"https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors\"><strong>EF Core interceptor</strong></a>.</p>\n<h2>Processing the Outbox</h2>\n<p>The Outbox processor is the next component we'll need.\nThis could be a <em>physically</em> separate process or a background worker in the same process.</p>\n<p>I'll use Quartz to <a href=\"https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net\"><strong>schedule background jobs</strong></a> for Outbox processing.\nIt's a robust library with excellent support for scheduling recurring jobs.</p>\n<p>Now, let's implement the <code>OutboxProcessorJob</code>:</p>\n<pre><code class=\"language-csharp\">[DisallowConcurrentExecution]\npublic class OutboxProcessorJob(\n    NpgsqlDataSource dataSource,\n    IPublishEndpoint publishEndpoint,\n    Assembly integrationEventsAssembly) : IJob\n{\n    public async Task Execute(IJobExecutionContext context)\n    {\n        await using var connection = await dataSource.OpenConnectionAsync();\n        await using var transaction = await connection.BeginTransactionAsync();\n\n        // You can make the limit a parameter, to control the batch size.\n        // We can also select just the id, type, and content columns.\n        var messages = await connection.QueryAsync&lt;OutboxMessage&gt;(\n            @&quot;&quot;&quot;\n            SELECT id AS Id, type AS Type, content AS Content\n            FROM outbox_messages\n            WHERE processed_on_utc IS NULL\n            ORDER BY occurred_on_utc LIMIT 100\n            &quot;&quot;&quot;,\n            transaction: transaction);\n\n        foreach (var message in messages)\n        {\n            try\n            {\n                var messageType = integrationEventsAssembly.GetType(message.Type);\n                var deserializedMessage = JsonSerializer.Deserialize(message.Content, messageType);\n\n                // We should introduce retries here to improve reliability.\n                await publishEndpoint.Publish(deserializedMessage);\n\n                await connection.ExecuteAsync(\n                    @&quot;&quot;&quot;\n                    UPDATE outbox_messages\n                    SET processed_on_utc = @ProcessedOnUtc\n                    WHERE id = @Id\n                    &quot;&quot;&quot;,\n                    new { ProcessedOnUtc = DateTime.UtcNow, message.Id },\n                    transaction: transaction);\n            }\n            catch (Exception ex)\n            {\n                // We can also introduce error logging here.\n\n                await connection.ExecuteAsync(\n                    @&quot;&quot;&quot;\n                    UPDATE outbox_messages\n                    SET processed_on_utc = @ProcessedOnUtc, error = @Error\n                    WHERE id = @Id\n                    &quot;&quot;&quot;,\n                    new { ProcessedOnUtc = DateTime.UtcNow, Error = ex.ToString(), message.Id },\n                    transaction: transaction);\n            }\n        }\n\n        await transaction.CommitAsync();\n    }\n}\n</code></pre>\n<p>This approach uses polling to periodically fetch unprocessed messages from the database.\nPolling can increase the load on the database, as we'll need to query for unprocessed messages frequently.</p>\n<p>An alternative way to process Outbox messages is by using <a href=\"https://microservices.io/patterns/data/transaction-log-tailing.html\">Transaction log tailing</a>.\nWe can implement this using <a href=\"https://www.npgsql.org/doc/replication.html\">Postgres logical replication</a>.\nThe database will stream changes from the Write-Ahead Log (WAL) to our application, and we'll process these messages and publish them to the message broker.\nYou can use this to implement a push-based Outbox processor.</p>\n<h2>Considerations and Tradeoffs</h2>\n<p>The Outbox pattern, while effective, introduces additional complexity and database writes.\nIn high-throughput systems, it's crucial to monitor its performance to ensure it doesn't become a bottleneck.</p>\n<p>I recommend implementing retry mechanisms in the Outbox processor to <a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\"><strong>improve reliability</strong></a>.\nConsider using exponential backoff for transient failures and a circuit breaker for persistent issues to prevent system overload during outages.</p>\n<p>It's essential that you implement <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>idempotent message consumers</strong></a>.\nNetwork issues or processor restarts can lead to multiple deliveries of the same message, so your consumers must handle repeated processing safely.</p>\n<p>Over time, the Outbox table can grow significantly, potentially impacting database performance.\nIt's important to implement an archiving strategy early on.\nConsider moving processed messages to cold storage or deleting them after a set period.</p>\n<h2>Scaling Outbox Processing</h2>\n<p>As your system grows, you may find that a single Outbox processor can't keep up with the volume of messages.\nThis can lead to increased latency between when an event occurs and when it's processed by consumers.</p>\n<p>One straightforward approach is to increase the frequency of the Outbox processor job.\nYou should consider running it every few seconds.\nThis can significantly reduce the delay in message processing.</p>\n<p>Another effective strategy is to increase the batch size when fetching unprocessed messages.\nBy processing more messages in each run, you can improve throughput.\nHowever, be cautious not to make the batches so large that they cause long-running transactions.</p>\n<p>For high-volume systems, processing the Outbox in parallel can be very effective.\nImplement a locking mechanism to claim batches of messages, allowing multiple processors to work simultaneously without conflict.\nYou can use <code>SELECT ... FOR UPDATE SKIP LOCKED</code> to claim a batch of messages.\nThis approach can dramatically increase your processing capacity.</p>\n<p>Here's an in-depth article on <a href=\"https://milanjovanovic.tech/blog/scaling-the-outbox-pattern\"><strong>scaling the outbox pattern</strong></a>\nthat covers these strategies in more detail.\nI'll show you how we can reach more than 30,000 messages per second (2B+ messages per day).</p>\n<h2>Wrapping Up</h2>\n<p>The Outbox pattern is a powerful tool for maintaining data consistency in distributed systems.\nBy decoupling database operations from message publishing, the Outbox pattern ensures that your system remains reliable even in the face of failures.</p>\n<p>Remember to keep your consumers idempotent, implement proper scaling strategies, and manage your Outbox table growth.</p>\n<p>While it adds some complexity, the benefits of guaranteed message delivery make it a valuable pattern in many scenarios.</p>\n<p>If you're looking to implement the Outbox pattern in a robust, production-ready way, you can check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.\nIt includes an entire section on implementing the Outbox pattern, along with other essential patterns for building maintainable and scalable .NET applications.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-the-outbox-pattern",
            "title": "Implementing the Outbox Pattern",
            "summary": "Discover how the Outbox pattern solves the dual-write problem in distributed systems, ensuring data consistency between your database and external components.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_110.png",
            "date_modified": "2024-10-05T00:00:00.000Z",
            "date_published": "2024-10-05T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/breaking-it-down-how-to-migrate-your-modular-monolith-to-microservices",
            "content_html": "<p>Migrate one module at a time.\nTighten module boundaries and data ownership first, then extract a low-coupling module into its own project and database.\nDirect method calls become HTTP or messaging calls, and an API gateway gives clients one entry point.</p>\n<p>As your application grows, you might find yourself considering a move from a modular monolith to microservices.\nThis transition isn't just a technical shift.\nIt's a strategic move that can reshape how your entire system operates.\nBut let's be clear: it's not a magic solution and comes with its own challenges.</p>\n<p>In this article, I'll share my experience of migrating from a modular monolith to microservices.</p>\n<p>We'll explore why you might consider microservices, how to prepare for the migration and the critical steps in the migration process -\nfrom choosing your first module to implementing inter-service communication and managing data migration.</p>\n<p>Let's dive in!</p>\n<h2>Why Consider Microservices?</h2>\n<p>A <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a> serves as an excellent starting point for many applications.\nIt's simpler to develop, easier to understand, and faster to deploy than a distributed system.\nBut as your application grows, you might start facing some problems.</p>\n<p>Here are the key challenges I've encountered with modular monoliths:</p>\n<ul>\n<li>High-load modules can become bottlenecks, affecting the entire system's performance</li>\n<li>As the monolith grows, deployments become riskier and more time-consuming</li>\n<li>Large teams working on a single codebase often step on each other's toes</li>\n<li>You're locked into a single technology stack for the entire application</li>\n</ul>\n<p>Microservices can address these issues, but it's not a decision to be taken lightly.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_109/modular_monolith_vs_microservices.png\" alt=\"Modular monolith to microservices comparison.\">\n<p>Here's how microservices can help:</p>\n<ul>\n<li><strong>Scalability</strong>: You can scale individual services based on their specific load, optimizing resource use.</li>\n<li><strong>Independent deployability</strong>: Updates to one service don't require redeploying the entire application, reducing risk and downtime.</li>\n<li><strong>Team autonomy</strong>: Separate teams can own different services, leading to faster development cycles.</li>\n</ul>\n<p>The good news? If you've built a well-designed modular monolith, you're already halfway there.\nThe clear boundaries between modules in your monolith can serve as a blueprint for your microservices architecture.</p>\n<p>Remember, microservices come with their own complexities in areas like data consistency and inter-service communication.\nBut for the right use cases, they can provide the flexibility and scalability needed for growing applications.</p>\n<h2>Preparing for Migration</h2>\n<p>The success of your migration largely depends on how well you prepare.\nYour best starting point is a well-structured modular monolith.</p>\n<p>Here are key areas to focus on:</p>\n<ol>\n<li>\n<p><strong>Review module boundaries</strong>:\nI can't stress this enough: <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>clear module boundaries</strong></a> are crucial.\nEach module should have a distinct responsibility and minimal dependencies on others.\nIf boundaries are blurry, refactor before migrating.</p>\n</li>\n<li>\n<p><strong>Ensure proper data encapsulation</strong>:\nModules should not directly access each other's data stores.\nI make sure each module owns and manages its data exclusively.\nNo shared tables, no direct database access across modules.\nThis clean separation makes it much easier to extract modules into microservices later.</p>\n</li>\n<li>\n<p><strong>Implement clean public APIs</strong>:\nDefine clear contracts between modules to facilitate future separation.\nModules should communicate through well-defined APIs, not by accessing each other's internals.</p>\n</li>\n</ol>\n<p>Here's an example of the above:</p>\n<pre><code class=\"language-csharp\">// Good: Clear module boundary\nnamespace BookingsModule;\n\npublic class CreateBooking\n{\n    private readonly IBookingRepository _bookingRepository;\n    private readonly IPaymentGateway _paymentGateway; // Public API\n\n    public CreateBooking(IBookingRepository bookingRepository, IPaymentGateway paymentGateway)\n    {\n        _bookingRepository = bookingRepository;\n        _paymentGateway = paymentGateway;\n    }\n\n    public async Task&lt;BookingResult&gt; CreateBookingAsync(BookingRequest request)\n    {\n        var booking = await _bookingRepository.CreateAsync(Booking.FromRequest(request));\n\n        // Accessing the other module's data through an abstraction\n        var paymentResult = await _paymentGateway.ProcessPaymentAsync(booking.Id, booking.TotalAmount);\n\n        return new BookingResult(booking, paymentResult);\n    }\n}\n</code></pre>\n<p>By focusing on these areas, you're not just preparing for migration - you're improving your modular monolith.\nEven if you decide not to migrate, these steps will make your system more maintainable and scalable.</p>\n<h2>Choosing and Extracting the First Module</h2>\n<p>Selecting the right module to extract first can set the tone for your entire migration.\nIn my experience, it's crucial to start with a module that's self-contained and has clear boundaries.</p>\n<p>When I'm evaluating modules, I look for these characteristics:</p>\n<ul>\n<li>Low coupling with other modules</li>\n<li>High cohesion within the module</li>\n<li>A distinct business function</li>\n<li>Potential performance or scalability gains from separation</li>\n</ul>\n<p>For instance, in an e-commerce system, I might choose the product catalog module.\nIt typically has a clear purpose, doesn't heavily depend on other modules, and could benefit from independent scaling.</p>\n<p>Once you've chosen your module, here's the extraction process I follow:</p>\n<ol>\n<li>Create a new project for the microservice.</li>\n<li>Move the module's code to the new project.\nThis often reveals hidden dependencies, which is valuable information.</li>\n<li>Update the dependencies, ensuring the microservice is self-contained.\nThis might involve copying some shared code or refactoring to remove unnecessary dependencies.</li>\n<li>Set up a separate database for the microservice.\nThis enforces data independence.</li>\n<li>Implement a data migration strategy (more on this later)</li>\n</ol>\n<p>Here's what this might look like in practice:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_109/modular_monolith_extraction.png\" alt=\"Modular monolith extraction process into microservices.\">\n<p>Remember, the goal isn't perfection on the first try.\nI often iterate on this process, gradually refining the separation between the new microservice and the monolith.</p>\n<p>The first extraction is a learning experience.\nIt'll show you how to approach subsequent modules and help you refine the overall migration strategy.</p>\n<h2>Implementing Inter-Service Communication</h2>\n<p>Once you've extracted a module into a microservice, you have to update the <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>module's communication</strong></a>\nwith the rest of the system.\nThis typically involves transitioning from direct method calls to network-based communication.</p>\n<p>Here's how I approach this transition:</p>\n<ol>\n<li>Replace direct method calls with HTTP API calls.\nI often use libraries like <a href=\"https://milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp\"><strong>Refit</strong></a> to simplify API interactions.\nHere's a before and after example:</li>\n</ol>\n<pre><code class=\"language-csharp\">// Before: Direct method call\nBookingDto booking = await _bookingService.GetAsync(bookingId);\n\n// After: HTTP API call\nvar response = await _httpClient.GetAsync($&quot;http://bookings-service/api/bookings/{bookingId}&quot;);\n\nvar booking = await response.Content.ReadFromJsonAsync&lt;BookingDto&gt;();\n</code></pre>\n<ol start=\"2\">\n<li>\n<p>For asynchronous communication, consider implementing a messaging system.\nWhile the implementation details vary, tools like <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>RabbitMQ</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit\"><strong>Amazon SQS and SNS</strong></a>,\nor <a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\"><strong>Azure Service Bus</strong></a>\ncan significantly improve system resilience and decoupling.</p>\n</li>\n<li>\n<p>Network communication introduces new failure modes, so I always implement <a href=\"https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet\"><strong>resilience patterns</strong></a>.\nHere's how I use Polly with resilience pipelines:</p>\n</li>\n</ol>\n<pre><code class=\"language-csharp\">using Polly;\n\nvar pipeline = new ResiliencePipelineBuilder&lt;HttpResponseMessage&gt;()\n    .AddRetry(new RetryStrategyOptions\n    {\n        ShouldHandle = new PredicateBuilder().Handle&lt;ConflictException&gt;(),\n        Delay = TimeSpan.FromSeconds(1),\n        MaxRetryAttempts = 2,\n        BackoffType = DelayBackoffType.Exponential,\n        UseJitter = true\n    })\n    .AddTimeout(new TimeoutStrategyOptions\n    {\n        Timeout = TimeSpan.FromSeconds(10)\n    })\n    .Build();\n\nvar response = await pipeline.ExecuteAsync(\n    async ct =&gt; await _httpClient.GetAsync($&quot;http://bookings-service/api/bookings/{bookingId}&quot;, ct),\n    cancellationToken);\n</code></pre>\n<p>Transitioning to HTTP-based communication always brings new problems to solve.\nSerialization, error handling, and <a href=\"https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore\"><strong>API versioning</strong></a> become crucial.\nI've learned to plan for these aspects from the start to avoid headaches down the line.</p>\n<h2>Implementing an API Gateway</h2>\n<p>As you extract more services, managing the communication between clients and your microservices can become complex.\nAn API Gateway can help manage this complexity by providing a single entry point for all clients.</p>\n<p>YARP (Yet Another Reverse Proxy) is an excellent tool for <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>implementing an API Gateway</strong></a> in .NET.\nHere's how I typically set it up:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddReverseProxy()\n    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;));\n\nvar app = builder.Build();\n\napp.MapReverseProxy();\n\napp.Run();\n</code></pre>\n<p>Then, I configure the routing in <code>appsettings.json</code>:</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;bookings-route&quot;: {\n        &quot;ClusterId&quot;: &quot;bookings-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/api/bookings/{**catch-all}&quot;\n        }\n      },\n      &quot;payments-route&quot;: {\n        &quot;ClusterId&quot;: &quot;payments-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/api/payments/{**catch-all}&quot;\n        }\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;bookings-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://bookings-service/&quot;\n          }\n        }\n      },\n      &quot;payments-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://payments-service/&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<p>This setup routes requests to the appropriate microservice based on the path.\nFor example, any request to <code>/api/bookings/*</code> will be routed to the <code>bookings-service</code>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_109/modular_monolith_extraction_api_gateway.png\" alt=\"Modular monolith extraction process into microservices with an API gateway introduced.\">\n<p>In my projects, I've found that an API Gateway often becomes a critical point for implementing cross-cutting concerns.\nAs your architecture evolves, consider adding features like:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/advanced-rate-limiting-use-cases-in-dotnet\"><strong>Rate limiting</strong></a> to protect your services from overload</li>\n<li><a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>Caching</strong></a> to improve response times for frequently requested data</li>\n<li>Request/response transformation to adapt your internal APIs for external consumption</li>\n</ul>\n<p>Start simple, but be prepared to evolve your gateway as you learn more about your system's needs and usage patterns.</p>\n<h2>Data Migration Strategy</h2>\n<p>Data migration is often the trickiest part of moving from a modular monolith to microservices. In my experience, there are two main approaches:</p>\n<ol>\n<li>\n<p><strong>The &quot;One and Done&quot; Approach</strong></p>\n<p>For simpler systems or those that can handle some downtime, I use this method:</p>\n<ul>\n<li>Create a new database for the microservice</li>\n<li>Copy the relevant schema and data from the monolith</li>\n<li>Switch the application to use the new database</li>\n</ul>\n<p>It's quick and simple but requires a brief downtime.</p>\n</li>\n<li>\n<p><strong>The Synchronization Approach</strong></p>\n<p>For complex systems needing minimal disruption, I use this method:</p>\n<ul>\n<li>Copy the schema and initial data to the new database</li>\n<li>Set up a sync mechanism (like <a href=\"https://en.wikipedia.org/wiki/Change_data_capture\">change data capture</a>) to keep the new database updated</li>\n<li>Gradually shift traffic to the new microservice</li>\n<li>Eventually, remove the old schema from the monolith</li>\n</ul>\n<p>This approach is more complex but allows for a smoother transition.</p>\n</li>\n</ol>\n<p>In both cases, I always ensure I have a solid backup and rollback plan.\nThe key is to choose the method that best fits your system's needs and tolerance for complexity.</p>\n<p>Remember, if your modular monolith already uses <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\"><strong>separate database schemas for each module</strong></a>, you're starting with an advantage.\nThis logical isolation makes the migration process significantly easier.</p>\n<h2>Summary</h2>\n<p>Migrating from a modular monolith to microservices is a journey I've taken several times, and it's always both challenging and rewarding.\nThe process we've covered here - from preparing your monolith and choosing the right module to extract\nto implementing proper communication patterns - forms the foundation of a successful migration.</p>\n<p>Don't rush the process.\nCareful planning and incremental changes are key.\nEach step teaches you something valuable about your system.</p>\n<p>While we've covered the fundamentals, there's always more to learn. I recommend exploring these advanced topics:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul\"><strong>Service discovery</strong></a> and API gateway patterns</li>\n<li><a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>Monitoring and observability</strong></a> in a microservices environment</li>\n<li>Advanced <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>deployment strategies</strong></a>, like blue-green deployment</li>\n</ul>\n<p>If you're ready to master this process and gain hands-on experience, I've put together a comprehensive\n<a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a> course.\nThere's an entire chapter dedicated to extracting modules and moving to microservices.\nYou'll gain practical skills to confidently navigate your microservices transformation.</p>\n<p>Remember, the goal isn't to blindly adopt microservices but to evolve your architecture to best serve your business needs.\nSometimes, a well-structured modular monolith is the right solution.\nOther times, a full microservices architecture is the way to go.\nThe key is understanding the trade-offs and making informed decisions.</p>\n<p>Good luck out there, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/breaking-it-down-how-to-migrate-your-modular-monolith-to-microservices",
            "title": "Breaking It Down: How to Migrate Your Modular Monolith to Microservices",
            "summary": "Moving from a modular monolith to microservices buys you independent scaling and deployment, and costs you a distributed system.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_109.png",
            "date_modified": "2024-09-28T00:00:00.000Z",
            "date_published": "2024-09-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-i-implemented-full-text-search-on-my-website",
            "content_html": "<p>I replaced an exact-match filter over a JSON file with a Lunr.js full-text index built at build time.\nThe client loads the index, ranks results with TF-IDF, and sorts by relevance instead of date.\nBrotli compression took the index from 2.5MB to 193KB.</p>\n<p>So, I've got this blog I've been running for a couple of years now.\nIt's a Next.js and TypeScript static site, all optimized for SEO.\nSounds pretty standard, right?\nWell, it was, until I ran into a problem.</p>\n<p>As my content grew, finding specific articles became a real pain.\nIt wasn't just me - readers were struggling too.\nWhat's the point of writing all this stuff if no one can find it when they need it?</p>\n<p>That's when I decided to look into full-text search.\nNow, implementing full-text search on a static site isn't exactly straightforward.\nIt's not like you can just slap a database on there and call it a day.\nYou've got to get creative.</p>\n<p>In this article, I'll walk you through how I turned my site's search from useless to actually functional.</p>\n<h2>The Old Search Approach</h2>\n<p>Here's how the search function worked before:</p>\n<ol>\n<li>I would generate a JSON document at build time containing each blog post's metadata.\nThis document contains the data needed to perform the search and render the results.</li>\n<li>When searching the articles, I would fetch the JSON document on the client and perform the search.\nSince this document changes only once a week, I can cache it on the client to improve performance.</li>\n</ol>\n<p>Here's the code that would generate the search data JSON document:</p>\n<pre><code class=\"language-ts\">export const generateSearchData = () =&gt; {\n  const posts = getAllPosts();\n\n  const searchData = posts.map((post) =&gt; ({\n    slug: post.meta.slug,\n    title: post.meta.title,\n    excerpt: post.meta.excerpt,\n    coverImage: post.meta.coverImage,\n    date: post.meta.date,\n    searchableContent:\n      `${post.meta.title} ${post.meta.excerpt} ${post.content}`.toLowerCase()\n  }));\n\n  fs.mkdirSync('./search', { recursive: true });\n  fs.writeFileSync(\n    path.join(process.cwd(), 'search', 'search-data.json'),\n    JSON.stringify(searchData)\n  );\n};\n</code></pre>\n<p>And then the search function is pretty simple (but not very smart):</p>\n<pre><code class=\"language-typescript\">export const search(keyword) = () =&gt; {\n  const res = await fetch('/search/search-data.json');\n\n  const searchData = await res.json();\n\n  return searchData.filter((p) =&gt;\n    p.searchableContent.includes(keyword.toLowerCase())\n  );\n}\n</code></pre>\n<p>This approach has a few issues:</p>\n<ul>\n<li>The search data JSON document is large (861KB) and will continue to grow</li>\n<li>It's slow on large datasets and won't scale well</li>\n<li>It uses an exact match to find results</li>\n<li>It doesn't rank results by relevance</li>\n</ul>\n<p>I wasn't happy with this, so here's how I implemented a much better solution.</p>\n<h2>Introducing Full-Text Search</h2>\n<p>To fix these problems, I turned to <strong>full-text search</strong> using <a href=\"https://lunrjs.com/\">Lunr.js</a>.\nBut what exactly is full-text search?</p>\n<p>Full-text search is a technique that allows fast and efficient searching of large volumes of text by creating an index of all words in a document collection\nand returning ranked results based on relevance to the search query.</p>\n<p>Full-text search works by:</p>\n<ol>\n<li><strong>Indexing</strong>: Breaking down text into individual words (tokens)</li>\n<li><strong>Stemming</strong>: Reducing words to their base form (e.g., &quot;running&quot; to &quot;run&quot;)</li>\n<li><strong>Ranking</strong>: Scoring results based on relevance</li>\n</ol>\n<p>Another common operation is stop word removal.\nCommon words that don't add much meaning (like &quot;the&quot;, &quot;and&quot;, &quot;is&quot;) are often removed to save space and improve relevance.</p>\n<p>At the heart of full-text search is an <a href=\"https://en.wikipedia.org/wiki/Inverted_index\">inverted index</a>.\nIt's a data structure that maps each unique term to the documents containing it.\nIt's &quot;inverted&quot; because it goes from terms to documents, rather than documents to terms.</p>\n<p>When searching, results are often ranked using <a href=\"https://en.wikipedia.org/wiki/Tf-idf\">TF-IDF</a> (Term Frequency-Inverse Document Frequency).\nThis gives higher scores to terms that are frequent in a document but rare across all documents.</p>\n<p>That's the rundown on full-text search. Let's see how to implement it.</p>\n<h2>Implementing Full-Text Search</h2>\n<p>I chose Lunr.js because it's lightweight and works great for static websites. No server required.</p>\n<p>I updated the <code>generateSearchData</code> function to create and store a full-text search index.\nThis function runs once at build time, so it's not expensive.</p>\n<pre><code class=\"language-typescript\">export const generateSearchData = () =&gt; {\n  const posts = getAllPosts();\n\n  // Generate search data as before.\n\n  const index = lunr(function () {\n    this.ref('slug');\n    this.field('title', { boost: 10 });\n    this.field('content', { boost: 5 });\n\n    searchData.forEach((doc) =&gt; {\n      this.add(doc);\n    });\n  });\n\n  // Store search data as before.\n\n  // And store the search index.\n  fs.writeFileSync(\n    path.join(process.cwd(), 'search', 'search-index.json'),\n    JSON.stringify(index)\n  );\n};\n</code></pre>\n<p>I'm <strong>boosting</strong> the title with a factor of 10 and the content with a factor of 5.\nIf the search term matches the title, it will have a higher relevance score.</p>\n<p>The downside (for now) is that the index file is big, 2.5MB.\nIf I were to download this on the client every time, the costs would quickly add up.\nI have 100,000 unique visitors per month, which would equate to <code>250GB</code> of network egress.</p>\n<p>Now, I have to update the search function to use the full-text index:</p>\n<pre><code class=\"language-typescript\">export const search(keyword) = () =&gt; {\n  const res = await fetch('/search/search-data.json');\n  const searchData = await res.json();\n\n  const res = await fetch('/search/search-index.json');\n  const searchIndex = lunr.Index.load(indexJson);\n\n  return searchIndex\n    .search(keyword)\n    .map((result) =&gt; {\n      const post = searchData?.find((post) =&gt; post.slug === result.ref);\n      return post ? { ...post, score: result.score } : null;\n    })\n    .filter((result) =&gt; result !== null)\n    .sort((a, b) =&gt; b.score - a.score);\n}\n</code></pre>\n<p>What's happening here:</p>\n<ul>\n<li>We're loading the search index from a JSON document using <code>lunr.Index.load</code></li>\n<li>The index has a <code>search</code> function allowing us to perform full-text search</li>\n<li>We're expanding the result to also include the relevance score</li>\n</ul>\n<p>This allows me to sort the search results based on relevance instead of chronologically.</p>\n<p>Here's what the old search implementation returns when searching for &quot;resilience&quot;:</p>\n<div className=\"bordered centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_108/old_search.png\" alt=\"The web page showing search results for the keyword \">\n</div>\n<p>The most relevant article is at the fourth spot, sorted chronologically.\nYou can see how this isn't a good user experience.</p>\n<p>But with full-text search, when you search for a keyword like &quot;resilience&quot;, you get nicer results:</p>\n<div className=\"bordered centered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_108/full_text_search.png\" alt=\"The web page showing full-text search results for the keyword \">\n</div>\n<p>I also added a relevance score next to each article because it looks cool.</p>\n<h2>Optimizing The Search</h2>\n<p>Now, I have a powerful search, but the index was huge (a whopping 2.5MB) and will continue to grow.\nThe search data file is also 861KB.</p>\n<p>Enter <a href=\"https://en.wikipedia.org/wiki/Brotli\">Brotli</a> compression:</p>\n<ul>\n<li>I compressed the search index and data using Brotli on the server side</li>\n<li>In the browser, I decompress the files before using them to perform the search</li>\n</ul>\n<pre><code class=\"language-typescript\">import { compress } from 'brotli';\n\nexport const generateSearchData = () =&gt; {\n  // Create the search data and index\n\n  fs.writeFileSync(\n    path.join(process.cwd(), 'search', 'search-index.br'),\n    compress(Buffer.from(JSON.stringify(index)))\n  );\n  fs.writeFileSync(\n    path.join(process.cwd(), 'search', 'search-data.br'),\n    compress(Buffer.from(JSON.stringify(searchData)))\n  );\n};\n</code></pre>\n<p>The results:</p>\n<ul>\n<li>2.5MB → 193KB <strong>(-92%)</strong></li>\n<li>861KB → 180KB <strong>(-79%)</strong></li>\n</ul>\n<p>If you want to learn more about compression algorithms, check out this <a href=\"https://milanjovanovic.tech/blog/response-compression-in-aspnetcore\"><strong>article about response compression</strong></a>.</p>\n<p>After fetching the compressed search data and full-text index on the client, I cache them for subsequent searches.</p>\n<p>The searches are lightning-fast now, and the results are more relevant.</p>\n<h2>Server-Side Options for Full-Text Search</h2>\n<p>While Lunr.js works great for my static site, larger apps with more data need a robust full-text search solution.\nSo, here are some some server-side options you could explore.</p>\n<p><a href=\"https://lucene.apache.org/\">Lucene</a> is the foundation of many search engines.\nIt's written in Java but has ports to other languages.\nLucene provides robust full-text indexing and search capabilities.\nIt is also highly efficient and customizable, making it a popular choice for developers who need fine-grained control over their search functionality.</p>\n<p><a href=\"https://solr.apache.org/\">Apache Solr</a> builds on top of Lucene, offering additional features like distributed indexing, replication, and load-balanced querying.\nSolr includes powerful capabilities such as faceting and highlighting, which can greatly enhance the search experience.</p>\n<p>If you're already using <a href=\"https://www.postgresql.org/\">PostgreSQL</a>, the built-in <a href=\"https://www.postgresql.org/docs/current/textsearch.html\">full-text search</a> is worth considering.\nPostgreSQL uses its own text search engine, which supports multiple languages and custom dictionaries.\nWhile not as feature-rich as dedicated search engines, it can be a convenient option for applications that want to keep their stack simple.</p>\n<p>I've been tinkering with PostgreSQL <a href=\"https://www.npgsql.org/efcore/mapping/full-text-search.html\">full-text search using EF Core</a>.\nHere's what a full-text search query looks like:</p>\n<pre><code class=\"language-csharp\">var blogs = context\n    .BlogPosts\n    .Where(b =&gt;\n        EF.Functions.ToTsVector(&quot;english&quot;, b.Title + &quot; &quot; + b.Excerpt + &quot; &quot; + b.Content)\n            .Matches(EF.Functions.PhraseToTsQuery(&quot;english&quot;, searchTerm)))\n    .Select(b =&gt; new\n    {\n        b.Slug,\n        b.Title,\n        b.Excerpt,\n        b.Date,\n        Rank = EF.Functions.ToTsVector(&quot;english&quot;, b.Title + &quot; &quot; + b.Excerpt + &quot; &quot; + b.Content)\n            .Rank(EF.Functions.PhraseToTsQuery(&quot;english&quot;, searchTerm))\n    })\n    .OrderByDescending(b =&gt; b.Rank)\n    .ToList();\n</code></pre>\n<p>But I'll have to write a dedicated article to cover this, so let's wrap it up.</p>\n<h2>Wrapping Up</h2>\n<p>The old search functionality was, frankly, embarrassing.</p>\n<p>It was slow, dumb as a rock, and about as useful as a chocolate teapot.</p>\n<p>Now? It's actually not half bad.</p>\n<ul>\n<li>Searches are lightning-fast</li>\n<li>Results are more relevant</li>\n<li>The user experience is much smoother</li>\n</ul>\n<p>I'm not gonna lie, there was a moment of frustration when I thought about giving up.\nBut I'm glad I stuck with it.\nThere's something satisfying about building it yourself, you know?</p>\n<p>It might not be pretty, but it works.\nAnd sometimes, that's all that matters.</p>\n<p>The Brotli compression bit was a pleasant surprise.\nI thought I'd end up with a massive index file that would make mobile users cry.\nBut nope, it all works smoothly.\nGo figure.</p>\n<p>Have you tried implementing search on your site? How'd it go?</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-i-implemented-full-text-search-on-my-website",
            "title": "How I Implemented Full-Text Search On My Website",
            "summary": "This article walks you through how I used Lunr.js to add powerful search capabilities to a Next.js static site, dealing with challenges like client-side…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_108.png",
            "date_modified": "2024-09-21T00:00:00.000Z",
            "date_published": "2024-09-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development",
            "content_html": "<p>.NET Aspire is Microsoft's opinionated stack for building distributed .NET applications.\nAn <code>AppHost</code> project declares your projects, databases, and message brokers in C#, and Aspire wires up connection strings, health checks, and OpenTelemetry for you.\nWhen I reviewed it in 2024, it replaced my <code>docker-compose.yml</code> for local development.</p>\n<p>I've been tinkering with .NET Aspire lately, and I've got some thoughts to share.\nIf you're curious about this new cloud-native development tool from Microsoft, stick around.\nI'll break down what's great, what's not, and how you can start using it.</p>\n<blockquote>\n<p>.NET Aspire is an opinionated, cloud-ready stack for building observable, production-ready, distributed applications.</p>\n</blockquote>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview\"><strong>.NET Aspire</strong></a> is Microsoft's latest offering for cloud-native application development.\nIt aims to simplify the process of building, deploying, and managing distributed applications.</p>\n<p>Distributed applications often consist of small applications that consume external services like databases, message brokers, and caching.\n.NET Aspire gives you a set of tools to make building distributed applications easier.</p>\n<h2>.NET Aspire Orchestration</h2>\n<p>How are you setting up a local development environment?\nI often use <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker Compose</strong></a> to configure my applications and run external services.\nIt's a simple setup, but you need to manage environment variables and connection strings.\nIf you're not familiar with Docker, it can prove to be quite tricky sometimes.</p>\n<p>Here's a <code>docker-compose.yml</code> file from a recent project:</p>\n<pre><code class=\"language-yml\">services:\n  contentplatform-api:\n    image: ${DOCKER_REGISTRY-}contentplatform-api\n    container_name: ContentPlatform.Api\n    build:\n      context: .\n      dockerfile: ContentPlatform.Api/Dockerfile\n    ports:\n      - 5000:8080\n      - 5001:8081\n\n  contentplatform-reporting-api:\n    image: ${DOCKER_REGISTRY-}contentplatform-reporting-api\n    container_name: ContentPlatform.Reporting.Api\n    build:\n      context: .\n      dockerfile: ContentPlatform.Reporting.Api/Dockerfile\n    ports:\n      - 6000:8080\n      - 6001:8081\n\n  contentplatform-presentation:\n    image: contentplatform-ui:latest\n    container_name: ContentPlatform.Presentation\n    environment:\n      - ASPNETCORE_ENVIRONMENT=Development\n    ports:\n      - 3000:80\n\n  contentplatform-db:\n    image: postgres:latest\n    container_name: ContentPlatform.Db\n    environment:\n      - POSTGRES_DB=contentplatform\n      - POSTGRES_USER=postgres\n      - POSTGRES_PASSWORD=postgres\n    volumes:\n      - ./.containers/db:/var/lib/postgresql/data\n    ports:\n      - 5432:5432\n\n  contentplatform-mq:\n    image: rabbitmq:management\n    container_name: ContentPlatform.RabbitMq\n    hostname: contentplatform-mq\n    volumes:\n      - ./.containers/queue/data/:/var/lib/rabbitmq\n      - ./.containers/queue/log/:/var/log/rabbitmq\n    environment:\n      RABBITMQ_DEFAULT_USER: guest\n      RABBITMQ_DEFAULT_PASS: guest\n</code></pre>\n<p>This sets up two APIs, a client application, PostgreSQL, and RabbitMQ.\nI also have to configure the connection strings manually to connect to these services.</p>\n<p>So, I decided to migrate this application to .NET Aspire and documented the process.</p>\n<p>You can right-click an existing project in Visual Studio and select <code>Add &gt; .NET Aspire Orchestrator Support...</code>.</p>\n<figure className=\"figure-center\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_107/aspire_orchestration.png\" alt=\"Context menu with \">\n  <figcaption>\n    Source:\n    <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/get-started/add-aspire-existing-app\">Microsoft</a>\n  </figcaption>\n</figure>\n<p>This will add an <code>AppHost</code> and <code>ServiceDefaults</code> project to your solution.\nYou will then repeat this for the remaining projects in your solution to enlist them all in Aspire orchestration.</p>\n<p>The <code>AppHost</code> project is responsible for orchestration.\nYou can define your entire application stack in a single, readable file.\nRunning the <code>AppHost</code> project from Visual Studio will start the required applications and services.</p>\n<p>Here's the setup for my application using Aspire:</p>\n<pre><code class=\"language-csharp\">IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args);\n\nvar postgres = builder.AddPostgres(&quot;contentplatform-db&quot;)\n    .WithPgAdmin();\n\nvar rabbitMq = builder.AddRabbitMQ(&quot;contentplatform-mq&quot;)\n    .WithManagementPlugin();\n\nbuilder.AddProject&lt;Projects.ContentPlatform_Api&gt;(&quot;contentplatform-api&quot;)\n    .WithReference(postgres)\n    .WithReference(rabbitMq);\n\nbuilder.AddProject&lt;Projects.ContentPlatform_Reporting_Api&gt;(&quot;contentplatform-reporting-api&quot;)\n    .WithReference(postgres)\n    .WithReference(rabbitMq);\n\nbuilder.AddProject&lt;Projects.ContentPlatform_Presentation&gt;(&quot;contentplatform-presentation&quot;);\n\nbuilder.Build().Run();\n</code></pre>\n<p>The Aspire version is much more concise and readable.\nAdding new services or changing configurations is straightforward.\nYou also get built-in observability.\nAspire includes tools for logging, metrics, and distributed tracing out of the box, making it easier to monitor and debug your applications.</p>\n<p>When you run the application, you can see your applications and services on the Aspire dashboard:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_107/content_platfrom_resources.png\" alt=\"Aspire dashboard resource views showing the application services and containers running.\">\n<h3>Orchestration - The Bad Parts</h3>\n<p>There are a few things I don't like with the current Aspire setup.</p>\n<p>The <code>AppHost</code> project needs to reference all other projects to enlist them in orchestration.\nIf your services are all in one solution, this might be fine.\nBut what about large microservices systems?</p>\n<p>We can go around this limitation by building a Docker image for an external service.\nThere's an <code>AddContainer</code> method that allows us to configure container resources.\nHowever, we won't be able to debug these services.</p>\n<p>The <code>ServiceDefaults</code> projects needs to be visible to all other applications.\nAgain, this works perfectly fine if everything is in one solution.\nWe can also distribute this project as a NuGet package for complex systems.</p>\n<h2>.NET Aspire Integrations</h2>\n<p>If you're wondering how I configured PostgreSQL and RabbitMQ in the previous example, this is made available using Aspire Integrations.\nThese are NuGet packages that allow you to integrate with popular services, such as Redis or PostgreSQL.\nAspire integrations take care of many cloud-native concerns for you, like adding health checks and telemetry.</p>\n<p>You can right-click on the <code>AppHost</code> project and select <code>Add &gt; .NET Aspire package...</code> to see the list of available integrations:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_107/aspire_integrations.png\" alt=\"NuGet browser view showing a list of .NET Aspire integrations.\">\n<p>If we want to add Redis to our project, we can install the <code>Aspire.Hosting.Redis</code> package.\nThen, we would configure the Redis integration in the <code>AppHost</code> project:</p>\n<pre><code class=\"language-csharp\">var builder = DistributedApplication.CreateBuilder(args);\n\n// Other service omitted for brevity\n\nvar redis = builder.AddRedis(&quot;contentplatform-cache&quot;);\n\nbuilder.AddProject&lt;Projects.ContentPlatform_Api&gt;(&quot;contentplatform-api&quot;)\n    .WithReference(postgres)\n    .WithReference(rabbitMq)\n    .WithReference(redis);\n\nbuilder.Build().Run();\n</code></pre>\n<p>You can find a list of supported <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/integrations-overview\">Aspire integrations</a> in the documentation.</p>\n<p>When you configure a resource with a known connection string format, Aspire automatically injects an environment variable.\nThe connection string name will have the same name as the respective resource.</p>\n<ul>\n<li><code>WithReference(postgres)</code> produces <code>ConnectionStrings__contentplatform-db=&quot;&lt;VALUE&gt;&quot;</code></li>\n<li><code>WithReference(rabbitMq)</code> produces <code>ConnectionStrings__contentplatform-mq=&quot;&lt;VALUE&gt;&quot;</code></li>\n<li><code>WithReference(redis)</code> produces <code>ConnectionStrings__contentplatform-cache=&quot;&lt;VALUE&gt;&quot;</code></li>\n</ul>\n<p>This lets you use logical connection string names when configuring your services:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddDbContext&lt;ApplicationDbContext&gt;(o =&gt;\n    o.UseNpgsql(builder.Configuration.GetConnectionString(&quot;contentplatform-db&quot;)));\n</code></pre>\n<h2>Service Defaults and OpenTelemetry</h2>\n<p>One of Aspire's killer features is its built-in observability stack.\nIt integrates <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>OpenTelemetry</strong></a>, providing distributed tracing, metrics, and logging out of the box.</p>\n<p>When you enlist a project in .NET Aspire orchestration, there are some updates made to the <code>Program</code> file automatically:</p>\n<ul>\n<li><code>AddServiceDefaults</code> is called to configure OpenTelemetry, health checks, and service discovery</li>\n<li><code>MapDefaultEndpoints</code> is called to expose the health check endpoint</li>\n</ul>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.AddServiceDefaults();\n\n// Other code omitted for brevity\n\nvar app = builder.Build();\n\napp.MapDefaultEndpoints();\n\n// Other code omitted for brevity\n\napp.Run();\n</code></pre>\n<p>You can customize <code>AddServiceDefaults</code> according to your requirements.\nFor example, if you're using <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>MassTransit</strong></a>, you can add the respective tracing configuration for this library.</p>\n<p>Here's the distributed traces view on the Aspire dashboard.\nYou can see a <code>POST</code> request hitting the <code>contentplatform-api</code> service, publishing an <code>ArticleCreatedEvent</code>, and consuming that message in the <code>contentplatform-reporting-api</code> service.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_107/content_platfrom_traces.png\" alt=\"Aspire dashboard traces views showing one distributed trace.\">\n<p>For local development, the .NET Aspire dashboard provides a UI for viewing telemetry data.\nIn a production environment, you can configure the OpenTelemetry server to receive telemetry data using the <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> environment variable.</p>\n<h2>Deploying .NET Aspire Applications</h2>\n<p>.NET Aspire simplifies the deployment process for distributed applications, especially when targeting Azure.\nTo deploy an Aspire application, you first generate a <strong>manifest file</strong> using the <code>dotnet run</code> command with specific parameters.\nThis manifest is a JSON file that describes all the resources defined in your Aspire project, including services, databases, and other dependencies.</p>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_107/aspire_manifest.png\" alt=\".NET Aspire manifest JSON file example.\">\n  <figcaption>\n    Source:\n    <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/deployment/manifest-format\">Microsoft</a>\n  </figcaption>\n</figure>\n<p>Deployment tools can use the manifest to set up the necessary infrastructure in your target environment.\nAspire generates the required configuration for Azure Container Apps or Kubernetes for Azure deployments.\nIt handles tasks like setting up networking, scaling services, and configuring monitoring automatically.</p>\n<p>Here's a simple example of generating a manifest:</p>\n<pre><code>dotnet run --project ContentPlatform.AppHost\\ContentPlatform.AppHost.csproj `\n    -- --publisher manifest --output-path ../aspire-manifest.json\n</code></pre>\n<p>This command creates a JSON manifest file that deployment tools can use to set up your application in the cloud or on-premises infrastructure.</p>\n<p>You can learn more about <a href=\"https://learn.microsoft.com/en-us/dotnet/aspire/deployment/overview\">Aspire deployment</a> in the documentation.</p>\n<h2>Summary</h2>\n<p>I've used .NET Aspire a lot lately, and I'm genuinely impressed.</p>\n<p>Aspire makes building complex systems much easier.\nI can set up a distributed system with just a few lines of C# code.\nThis is much simpler than using Docker Compose.\nThe built-in observability and monitoring tools are also great.</p>\n<p>While .NET Aspire is now production-ready, the ecosystem around it is still growing.\nDevelopers, particularly those new to cloud-native concepts, might face a learning curve.</p>\n<p>Should you adopt Aspire in your .NET projects?</p>\n<p>If you're building distributed applications, especially for Azure, I'd say give it a try.\nHowever, you might want to evaluate carefully if you work on simpler applications or use non-Azure cloud services.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<p><strong>P.S.</strong> You can find the source code for this example in <a href=\"https://github.com/m-jovanovic/aspire-orchestration\"><strong>this repository</strong></a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/dotnet-aspire-a-game-changer-for-cloud-native-development",
            "title": ".NET Aspire: A Game-Changer for Cloud-Native Development?",
            "summary": ".NET Aspire promises to simplify cloud-native .NET development, but does it live up to the hype?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_107.png",
            "date_modified": "2024-09-14T00:00:00.000Z",
            "date_published": "2024-09-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp",
            "content_html": "<p>Refit turns an HTTP API into a C# interface.\nYou declare methods with attributes like <code>[Get]</code> and <code>[Post]</code>, and Refit generates the client that handles the requests and JSON serialization.\nRegister it with <code>AddRefitClient&lt;T&gt;()</code> and inject the interface wherever you need it.</p>\n<p>As a .NET developer, I've spent countless hours working with external APIs.\nIt's a crucial part of modern software development, but let's be honest - it can be a real pain sometimes.</p>\n<p>We've all been there, wrestling with <a href=\"https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet\"><strong><code>HttpClient</code></strong></a>, writing repetitive code, and hoping we didn't miss a parameter or header somewhere.</p>\n<p>That's why I want to introduce you to <strong>Refit</strong>, a library that's been a game-changer for me.</p>\n<p>Imagine turning your API into a live interface - sounds too good to be true, right?\nBut that's exactly what Refit does.\nIt handles all the HTTP heavy lifting, letting you focus on what matters: your application logic.</p>\n<p>In this article, I'll explain how Refit can transform the way you work with APIs in your .NET projects.</p>\n<h2>What is Refit?</h2>\n<p><a href=\"https://github.com/reactiveui/refit\">Refit</a> is a type-safe REST library for .NET.\nIt allows you to define your API as an interface, which Refit then implements for you.\nThis approach reduces boilerplate code and makes your API calls more readable and maintainable.</p>\n<p>You describe your API endpoints using method signatures and attributes, and Refit takes care of the rest.</p>\n<p>Let me break down why I find Refit so powerful:</p>\n<ul>\n<li><strong>Automatic serialization and deserialization</strong>:\nYou won't have to convert your objects to JSON and back.\nRefit handles all of that for you.</li>\n<li><strong>Strongly-typed API definitions</strong>:\nRefit helps you catch errors early.\nIf you mistype a parameter or use the wrong data type, you'll know at compile time, not when your app crashes in production.</li>\n<li><strong>Support for various HTTP methods</strong>:\nGET, POST, PUT, PATCH, DELETE - Refit has you covered.</li>\n<li><strong>Request/response manipulations</strong>:\nYou can add custom headers or handle specific content types in a straightforward way.</li>\n</ul>\n<p>But what I appreciate most about Refit is how it promotes clean, readable code.\nYour API calls become self-documenting.\nAnyone reading your code can quickly understand what each method does without diving into implementation details.</p>\n<h2>Setting Up and Using Refit in Your Project</h2>\n<p>Let's set up Refit and see it in action using the <a href=\"https://jsonplaceholder.typicode.com/\">JSONPlaceholder API</a>.\nWe'll implement a full CRUD interface and demonstrate its usage in a <a href=\"https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore\"><strong>Minimal API application</strong></a>.</p>\n<p>First, install the required NuGet packages:</p>\n<pre><code class=\"language-powershell\">Install-Package Refit\nInstall-Package Refit.HttpClientFactory\n</code></pre>\n<p>Now, let's create our Refit interface:</p>\n<pre><code class=\"language-csharp\">using Refit;\n\npublic interface IBlogApi\n{\n    [Get(&quot;/posts/{id}&quot;)]\n    Task&lt;Post&gt; GetPostAsync(int id);\n\n    [Get(&quot;/posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetPostsAsync();\n\n    [Post(&quot;/posts&quot;)]\n    Task&lt;Post&gt; CreatePostAsync([Body] Post post);\n\n    [Put(&quot;/posts/{id}&quot;)]\n    Task&lt;Post&gt; UpdatePostAsync(int id, [Body] Post post);\n\n    [Delete(&quot;/posts/{id}&quot;)]\n    Task DeletePostAsync(int id);\n}\n\npublic class Post\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string Body { get; set; }\n    public int UserId { get; set; }\n}\n</code></pre>\n<p>We define our <code>IBlogApi</code> interface with methods for all CRUD operations: GET (single and list), POST, PUT, and DELETE.\nThe <code>Post</code> class represents the structure of our blog posts.</p>\n<p>Then you have to register Refit in your dependency injection container:</p>\n<pre><code class=\"language-csharp\">using Refit;\n\nbuilder.Services\n    .AddRefitClient&lt;IBlogApi&gt;()\n    .ConfigureHttpClient(c =&gt; c.BaseAddress = new Uri(&quot;https://jsonplaceholder.typicode.com&quot;));\n</code></pre>\n<p>Finally, we can use <code>IBlogApi</code> in our Minimal API endpoints:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/posts/{id}&quot;, async (int id, IBlogApi api) =&gt;\n    await api.GetPostAsync(id));\n\napp.MapGet(&quot;/posts&quot;, async (IBlogApi api) =&gt;\n    await api.GetPostsAsync());\n\napp.MapPost(&quot;/posts&quot;, async ([FromBody] Post post, IBlogApi api) =&gt;\n    await api.CreatePostAsync(post));\n\napp.MapPut(&quot;/posts/{id}&quot;, async (int id, [FromBody] Post post, IBlogApi api) =&gt;\n    await api.UpdatePostAsync(id, post));\n\napp.MapDelete(&quot;/posts/{id}&quot;, async (int id, IBlogApi api) =&gt;\n    await api.DeletePostAsync(id));\n</code></pre>\n<p>What I love about this setup is its simplicity.\nWe've created a fully functional API that communicates with an external service, all in just a few lines of code.\nNo manual HTTP requests, no raw JSON handling - Refit takes care of all that for us.</p>\n<h2>Query Parameters and Route Binding</h2>\n<p>When working with APIs, you often need to send data as part of the URL, either in the route or as query parameters.\nRefit makes this process simple and type-safe.</p>\n<p>Let's extend our <code>IBlogApi</code> interface with some more complex scenarios:</p>\n<pre><code class=\"language-csharp\">public interface IBlogApi\n{\n    // Other methods omitted for brevity\n\n    [Get(&quot;/posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetPostsAsync([Query] PostQueryParameters parameters);\n\n    [Get(&quot;/users/{userId}/posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetUserPostsAsync(int userId);\n}\n\npublic class PostQueryParameters\n{\n    public int? UserId { get; set; }\n    public string? Title { get; set; }\n}\n</code></pre>\n<p>Let's break this down:</p>\n<ul>\n<li><code>GetPostsAsync</code> uses an object to represent query parameters.\nThis approach is excellent for endpoints with many optional parameters.\nRefit will automatically convert this object into a query string.</li>\n<li><code>GetUserPostsAsync</code> demonstrates passing in route parameters (<code>userId</code>) directly.</li>\n</ul>\n<p>Using an object for query parameters makes your code type-safe and <a href=\"https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips\"><strong>refactoring-friendly</strong></a>.\nIf you need to add a new query parameter, you just add a property to <code>PostQueryParameters</code>.\nYour existing code won't break, and your IDE can help you discover the new options.</p>\n<h2>Dynamic Headers and Authentication</h2>\n<p>Another common requirement when integrating with APIs is including custom headers or authentication tokens with your requests.\nRefit provides several ways to handle this, from simple static headers to dynamic, request-specific authentication.</p>\n<p>Let's explore some scenarios:</p>\n<pre><code class=\"language-csharp\">public interface IBlogApi\n{\n    [Headers(&quot;User-Agent: MyAwesomeApp/1.0&quot;)]\n    [Get(&quot;/posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetPostsAsync();\n\n    [Get(&quot;/secure-posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetSecurePostsAsync([Header(&quot;Authorization&quot;)] string bearerToken);\n\n    [Get(&quot;/user-posts&quot;)]\n    Task&lt;List&lt;Post&gt;&gt; GetUserPostsAsync([Authorize(scheme: &quot;Bearer&quot;)] string token);\n}\n</code></pre>\n<ul>\n<li>You can add a static header to all requests by using the <code>Headers</code> attribute</li>\n<li>With the <code>Header</code> attribute, you can pass a header value dynamically as a parameter</li>\n<li>The <code>Authorize</code> attribute is a convenient way to add Bearer token authentication</li>\n</ul>\n<p>But what if you need to add the same dynamic header to all requests?</p>\n<p>That's where <code>DelegatingHandler</code> comes in handy.</p>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore\"><strong>using delegating handlers in this article</strong></a>.</p>\n<p>I've found delegating handlers especially helpful in providing API keys, which are typically static.</p>\n<h2>JSON Serialization Options</h2>\n<p>Refit gives you flexibility when choosing and configuring your JSON serializer.\nBy default, Refit uses <code>System.Text.Json</code>, is the built-in JSON serializer in modern .NET versions.</p>\n<p>However, you can easily switch to <code>Newtonsoft.Json</code> if you need its features.</p>\n<p>Here's how you can configure Refit to use it.</p>\n<p>First, install the Newtonsoft.Json support package:</p>\n<pre><code class=\"language-powershell\">Install-Package Refit.Newtonsoft.Json\n</code></pre>\n<p>Then, configure Refit to use Newtonsoft.Json:</p>\n<pre><code class=\"language-csharp\">using Newtonsoft.Json;\nusing Newtonsoft.Json.Serialization;\nusing Refit;\n\nbuilder.Services.AddRefitClient&lt;IBlogApi&gt;(new RefitSettings\n{\n    ContentSerializer = new NewtonsoftJsonContentSerializer(new JsonSerializerSettings\n    {\n        ContractResolver = new CamelCasePropertyNamesContractResolver(),\n        NullValueHandling = NullValueHandling.Ignore\n    })\n})\n.ConfigureHttpClient(c =&gt; c.BaseAddress = new Uri(&quot;https://jsonplaceholder.typicode.com&quot;));\n</code></pre>\n<p>This setup uses camel case for property names and ignores null values when serializing.</p>\n<p><code>System.Text.Json</code> is faster and uses less memory, making it a great default choice.\nHowever, <code>Newtonsoft.Json</code> offers more features and might be necessary for compatibility with older systems or specific serialization needs.</p>\n<h2>Handling HTTP Responses</h2>\n<p>While Refit's default behavior of automatically deserializing responses into your defined types is convenient, there are times when you need more control over the HTTP response.</p>\n<p>Refit provides two options for these scenarios:\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage\"><code>HttpResponseMessage</code></a> and <code>ApiResponse&lt;T&gt;</code>.</p>\n<p>Let's update the <code>IBlogApi</code> to use these types:</p>\n<pre><code class=\"language-csharp\">public interface IBlogApi\n{\n    [Get(&quot;/posts/{id}&quot;)]\n    Task&lt;HttpResponseMessage&gt; GetPostRawAsync(int id);\n\n    [Get(&quot;/posts/{id}&quot;)]\n    Task&lt;ApiResponse&lt;Post&gt;&gt; GetPostWithMetadataAsync(int id);\n\n    [Post(&quot;/posts&quot;)]\n    Task&lt;ApiResponse&lt;Post&gt;&gt; CreatePostAsync([Body] Post post);\n}\n</code></pre>\n<p>Now, let's break down how to use these, starting with <code>HttpResponseMessage</code>:</p>\n<pre><code class=\"language-csharp\">HttpResponseMessage response = await blogApi.GetPostRawAsync(1);\n\nif (response.IsSuccessStatusCode)\n{\n    var content = await response.Content.ReadAsStringAsync();\n    var post = JsonSerializer.Deserialize&lt;Post&gt;(content);\n    Console.WriteLine($&quot;Retrieved post: {post.Title}&quot;);\n}\nelse\n{\n    Console.WriteLine($&quot;Error: {response.StatusCode}&quot;);\n}\n</code></pre>\n<p>This approach gives you full control over the HTTP response. You can access status codes, headers, and the raw content.\nBut you will have to deal with deserialization manually.</p>\n<p><code>ApiResponse&lt;T&gt;</code> is a Refit-specific type that wraps the deserialized content and response metadata.\nIt's a great middle ground when you need the typed response and access to headers or status codes.</p>\n<p>Here's a more complex example using <code>ApiResponse&lt;T&gt;</code> for creating a post:</p>\n<pre><code class=\"language-csharp\">var newPost = new Post { Title = &quot;New Post&quot;, Body = &quot;Content&quot;, UserId = 1 };\n\nApiResponse&lt;Post&gt; createResponse = await blogApi.CreatePostAsync(newPost);\n\nif (createResponse.IsSuccessStatusCode)\n{\n    var createdPost = createResponse.Content;\n    var locationHeader = createResponse.Headers.Location;\n    Console.WriteLine($&quot;Created post with ID: {createdPost.Id}&quot;);\n    Console.WriteLine($&quot;Location: {locationHeader}&quot;);\n}\nelse\n{\n    Console.WriteLine($&quot;Error: {createResponse.Error.Content}&quot;);\n    Console.WriteLine($&quot;Status: {createResponse.StatusCode}&quot;);\n}\n</code></pre>\n<p>This approach allows you to access the created resource, check specific headers like <code>Location</code>, and handle errors gracefully.</p>\n<h2>Takeaway</h2>\n<p>Refit transforms the way we interact with APIs in .NET applications.\nConverting your API into a strongly typed interface simplifies your code, enhances type safety, and improves maintainability.</p>\n<p>The key Refit benefits we've explored include:</p>\n<ul>\n<li>Simplified API calls with automatic serialization and deserialization</li>\n<li>Flexible parameter handling for complex queries</li>\n<li>Easy management of headers and authentication</li>\n<li>Options for JSON serialization to fit your project's needs</li>\n<li>Granular control over HTTP responses when required</li>\n</ul>\n<p>In my experience, Refit shines in projects of all sizes.\nI've used it in small applications and large-scale <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>microservices architectures</strong></a>.\nIt eliminates boilerplate code, reduces the risk of errors, and allows you to focus on your application's core logic rather than the intricacies of HTTP communication.</p>\n<p>Remember, while Refit makes API interactions more straightforward, it's not a substitute for understanding the underlying principles of RESTful communication and HTTP.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<p><strong>P.S.</strong> You can find the source code for this example in <a href=\"https://github.com/m-jovanovic/refit-client-example\"><strong>this repository</strong></a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp",
            "title": "Refit in .NET: Building Robust API Clients in C#",
            "summary": "Refit turns your HTTP API into a strongly typed C# interface, so you stop hand-writing HttpClient boilerplate.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_106.png",
            "date_modified": "2024-09-07T00:00:00.000Z",
            "date_published": "2024-09-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/introduction-to-event-sourcing-for-net-developers",
            "content_html": "<p>Event sourcing stores every change to your data as an immutable event in an append-only log instead of storing only the current state.\nYou rebuild state by replaying the events in order.\nThe tradeoff is a steep learning curve, plus slower state reconstruction as the event history grows.</p>\n<p>I've been coding in .NET for years, but I never built an event sourced system.\nEvent sourcing has always intrigued me, though.\nThe idea of capturing every change and having a complete history of your data - it's fascinating.</p>\n<p>So, I decided to dive in.\nNot as an expert but as a curious developer.</p>\n<p>In this newsletter, I'm sharing my journey into event sourcing.</p>\n<ul>\n<li>What is it really?</li>\n<li>Why does it matter?</li>\n<li>And how might it change the way we think about our .NET apps?</li>\n</ul>\n<p>We'll look at the core concepts of event sourcing, potential benefits, and even some practical examples.</p>\n<h2>What is Event Sourcing?</h2>\n<blockquote>\n<p>Event Sourcing is an architectural design pattern where changes that occur in a domain are immutably stored as events in an append-only log.</p>\n</blockquote>\n<p><em>— <a href=\"https://www.eventstore.com/event-sourcing\">Event Store</a></em></p>\n<p>When I first encountered event sourcing, it seemed complex.\nBut stripped down, it's a surprisingly simple idea: store changes, not just the current state.</p>\n<p>Think of a bank account or wallet.\nNormally, we'd just save the balance.\nWith event sourcing, we record every deposit and withdrawal.\nThe balance is then calculated from these events.</p>\n<p>This diagram illustrates the difference:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_105/event_sourcing.png\" alt=\"Event sourcing comparison to traditional data storage.\">\n<p>This shift from storing state to storing events is the essence of event sourcing.\nIt's like keeping a detailed diary of your application's data rather than just a snapshot.\nIt's not just about where you are but how you got there.\nFor me, this was a lightbulb moment.</p>\n<h2>Why Use Event Sourcing?</h2>\n<p>As I dug deeper into event sourcing, I kept asking myself: &quot;Why would I use this instead of traditional data storage?&quot;</p>\n<p>Here's what I've discovered:</p>\n<ul>\n<li><strong>Full Audit Trail</strong>: Every change is recorded.\nThis is huge for businesses dealing with sensitive data or financial transactions.\nImagine being able to trace every step of an order's journey or every modification to a user's account.</li>\n<li><strong>Debugging Time Machine</strong>: With event sourcing, you can reconstruct the state of your application at any point in time.\nAs a developer, this feels like a superpower.\nTracking down bugs becomes less about guesswork and more about replay.</li>\n<li><strong>Business Insights</strong>: All those stored events?\nThey're a goldmine of data.\nYou can analyze patterns, user behavior, or system performance in ways that might be impossible with just current-state data.</li>\n<li><strong>Flexibility</strong>: Need to add a new feature that requires historical data?\nWith event sourcing, it's already there.\nThis flexibility could have saved me from many &quot;I wish we had kept that information&quot; moments.</li>\n</ul>\n<p>Real-world use cases for event sourcing started to make sense:</p>\n<ul>\n<li>E-commerce platforms leveraging it for order tracking and inventory management.</li>\n<li>Financial systems use it for accurate transaction histories.</li>\n<li>IoT applications use it to analyze sensor data over time.</li>\n</ul>\n<p>While it's not a silver bullet (what is in programming?), I'm beginning to see why so many developers are excited about event sourcing.\nIt's not just about storing data; it's about intent and behavior.</p>\n<h2>Core Concepts And Practical Examples</h2>\n<p>As I started to implement event sourcing, understanding the core concepts became much easier with a concrete example.\nLet's walk through a simple bank account scenario to see how event sourcing works.</p>\n<h3>Events</h3>\n<p>Events are immutable records of something that happened.\nIn our bank account example, we might have events like <code>AccountOpened</code>, <code>MoneyDeposited</code>, and <code>MoneyWithdrawn</code>.</p>\n<p>Here's how we might define these in C#:</p>\n<pre><code class=\"language-csharp\">public record AccountOpened(Guid AccountId, DateTime OpenedAt);\npublic record MoneyDeposited(decimal Amount, DateTime DepositedAt);\npublic record MoneyWithdrawn(decimal Amount, DateTime WithdrawnAt);\n</code></pre>\n<p><a href=\"https://milanjovanovic.tech/blog/records-anonymous-types-non-destructive-mutation\"><strong>Records</strong></a> are a perfect fit for events, as they are immutable by design.</p>\n<h3>State</h3>\n<p>In event sourcing, the current state is calculated by applying all events in order.</p>\n<p>Here's how our <code>Account</code> class looks:</p>\n<pre><code class=\"language-csharp\">public class Account\n{\n    public Guid Id { get; private set; }\n    public decimal Balance { get; private set; }\n\n    private List&lt;object&gt; _events = new List&lt;object&gt;();\n\n    public Account(Guid id)\n    {\n        ApplyEvent(new AccountCreated(id));\n    }\n\n    public void Deposit(decimal amount)\n    {\n        ApplyEvent(new MoneyDeposited(amount));\n    }\n\n    public void Withdraw(decimal amount)\n    {\n        if (Balance &gt;= amount)\n        {\n            ApplyEvent(new MoneyWithdrawn(amount));\n        }\n        else\n        {\n            throw new InvalidOperationException(&quot;Insufficient funds&quot;);\n        }\n    }\n\n    private void ApplyEvent(object @event)\n    {\n        _events.Add(@event);\n\n        switch (@event)\n        {\n            case AccountCreated e:\n                Id = e.AccountId;\n                Balance = 0;\n                break;\n            case MoneyDeposited e:\n                Balance += e.Amount;\n                break;\n            case MoneyWithdrawn e:\n                Balance -= e.Amount;\n                break;\n        }\n    }\n}\n</code></pre>\n<p>Notice how the <code>Account</code> class maintains its state.\nEach method (<code>Deposit</code>, <code>Withdraw</code>) doesn't directly modify the balance.\nInstead, it creates and applies an event.\nThe <code>ApplyEvent</code> method then updates the state based on these events.</p>\n<h3>Event Store</h3>\n<p>In our simple example, we're using a list (<code>_events</code>) to store events.\nIn a real system, we would persist these events in a database.\nThe key principle remains: events are appended, never modified.</p>\n<p>For production systems, there are specialized event sourcing databases like <a href=\"https://www.eventstore.com/\">EventStoreDB</a>.</p>\n<p>There's also <a href=\"https://milanjovanovic.tech/blog/fast-document-database-in-net-with-marten\"><strong>Marten</strong></a>, a .NET library that adds document database and event sourcing capabilities to PostgreSQL.</p>\n<h2>Putting It All Together</h2>\n<p>Here's how we might use our event sourced <code>Account</code>:</p>\n<ul>\n<li>An action (like depositing money) triggers the creation of an event.</li>\n<li>The event is stored in the event store (in our simple example, it's just added to the <code>_events</code> list).</li>\n<li>The event is applied to update the current state of the <code>Account</code>.</li>\n<li>We can rebuild the state by replaying all events in order when needed.</li>\n</ul>\n<pre><code class=\"language-csharp\">var account = new Account(Guid.NewGuid());\naccount.Deposit(100);\naccount.Withdraw(30);\naccount.Deposit(50);\n\nConsole.WriteLine($&quot;Final balance: {account.Balance}&quot;); // Output: Final balance: 120\n</code></pre>\n<p>We'd store these events in a database in a real event sourcing system.\nThis allows us to replay the events on demand to produce the current state.</p>\n<h2>Challenges and Considerations</h2>\n<p>Since I started researching event sourcing, I've seen its potential and its hurdles.</p>\n<p>Event sourcing itself is a simple idea.\nHowever, the underlying complexity of this approach concerns me.\nThere's a significant learning curve from event sourcing basics to <em>applying event sourcing in production</em>.</p>\n<p>It's not just about storing data differently.\nIt's a fundamental shift in how you model and think about your domain.\nThis complexity extends to the infrastructure level.</p>\n<p>Performance is another consideration that's often overlooked.\nWhile appending events is typically fast, reconstructing the current state from a long history of events can be slow.\nReal-world systems often need to implement caching strategies or snapshots to mitigate this.\nEvent sourcing is also eventually consistent on the read side.</p>\n<p>One of the trickiest aspects I've encountered is event schema evolution (event versioning).\nAs your system grows and changes, so will your events.\nManaging these changes without breaking existing event streams is a challenge that requires careful planning and design.\nI'm still researching best practices.</p>\n<h2>In Summary</h2>\n<p>Event sourcing has a steep learning curve, even for an experienced developer.\nIt requires a fundamental shift in how you think about data and system design.</p>\n<p>If you want to give it a try, start small.\nImplement a simple event-sourced system in a side project.\nIt's the best way to grapple with the concepts hands-on.\nAs you do, you might find that Domain-Driven Design (DDD) principles align well with event sourcing.</p>\n<p>Remember, the goal isn't to use event sourcing everywhere but to understand where it can add value.</p>\n<p>If you're ready to explore this topic further, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.\nThere's an entire chapter on Event-Driven Architecture, which directly complements what you've learned about event sourcing here.</p>\n<p>In a future newsletter, we'll explore a more real-world application of event sourcing.</p>\n<p>Good luck out there, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/introduction-to-event-sourcing-for-net-developers",
            "title": "Introduction to Event Sourcing for .NET Developers",
            "summary": "Discover event sourcing in .NET through a beginner's eyes. Explore core concepts, benefits, and real-world challenges.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_105.png",
            "date_modified": "2024-08-31T00:00:00.000Z",
            "date_published": "2024-08-31T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/screaming-architecture",
            "content_html": "<p>Screaming architecture means your folder structure tells a reader what the system does, not which framework it runs on.\nYou get there by making use cases and feature folders the top-level concept instead of <code>Controllers</code>, <code>Services</code>, and <code>Repositories</code>.\nThe payoff is higher cohesion and easier navigation.</p>\n<p>If you were to glance at the folder structure of your system, could you tell what the system is about?\nAnd here's a more interesting question.\nCould a new developer on your team easily understand what the system does based on the folder structure?</p>\n<p>Your architecture should communicate what problems it solves.\nOrganizing your system around use cases leads to a structure aligned with the business domain.\nThis approach is called <strong>screaming architecture</strong>.</p>\n<p><a href=\"https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html\">Screaming architecture</a> is a term coined by Robert Martin (Uncle Bob).\nHe argues that a software system's structure should communicate what the system is about.\nHe draws a parallel between looking at a blueprint for a building, where you can tell the purpose of the building based on the blueprint.</p>\n<p>In this article, I want to show some practical examples and discuss the benefits of screaming architecture.</p>\n<h2>A Use Case Driven Approach</h2>\n<p>A use case represents a specific interaction or task that a user wants to achieve within your system.\nIt encapsulates the business logic required to fulfill that task.\nA use case is a high-level description of a user's goal.\nFor example, &quot;reserving an apartment&quot; or &quot;purchasing a ticket&quot;.\nIt focuses on the <em>what</em> of the system's behavior, not the <em>how</em>.</p>\n<p>When you look at the folder structure and source code files of your system:</p>\n<ul>\n<li>Do they scream: Apartment Booking System or Ticketing System?</li>\n<li>Or do they scream ASP.NET Core?</li>\n</ul>\n<p>Here's an example of a folder structure organized around technical concerns:</p>\n<pre><code class=\"language-powershell\">📁 Api/\n|__ 📁 Controllers\n|__ 📁 Entities\n|__ 📁 Exceptions\n|__ 📁 Repositories\n|__ 📁 Services\n    |__ #️⃣ ApartmentService.cs\n    |__ #️⃣ BookingService.cs\n    |__ ...\n|__ 📁 Models\n</code></pre>\n<p>Somewhere inside these folders, we'll find concrete classes that contain the system's behavior.\nYou'll notice that the cohesion with this folder structure is low.</p>\n<p>How does screaming architecture help?</p>\n<p>A use case driven approach will place the system's use cases as the top-level concept.\nI also like to group related use cases into a top-level <a href=\"https://milanjovanovic.tech/blog/feature-folders-dotnet\"><strong>feature folder</strong></a>.\nInside a use case folder, we may find technical concepts required to implement it.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\"><strong>Vertical slice architecture</strong></a> also approaches this from a similar perspective.</p>\n<pre><code class=\"language-powershell\">📁 Api/\n|__ 📁 Apartments\n    |__ 📁 ReserveApartment\n    |__ ...\n|__ 📁 Bookings\n    |__ 📁 CancelBooking\n    |__ ...\n|__ 📁 Payments\n|__ 📁 Reviews\n|__ 📁 Disputes\n|__ 📁 Invoicing\n</code></pre>\n<p>The use case driven folder structure helps us better understand user needs and aligns development efforts with business goals.</p>\n<h2>Screaming Architecture Benefits</h2>\n<p>The benefits of organizing our system around use cases are:</p>\n<ul>\n<li>Improved cohesion since related use cases are close together</li>\n<li>High coupling for a single use case and its related use cases</li>\n<li>Low coupling between unrelated use cases</li>\n<li>Easier navigation through the solution</li>\n</ul>\n<h2>Bounded Contexts and Vertical Slices</h2>\n<p>We have many techniques for discovering the high-level modules within our system.\nFor example, we could use <a href=\"https://www.eventstorming.com/\">event storming</a> to explore the system's use cases.\nDomain exploration happens before we write a single line of code.</p>\n<p>The next step is decomposing the larger problem domain into smaller sub-domains and later <a href=\"https://milanjovanovic.tech/blog/bounded-context-ddd-explained\"><strong>bounded contexts</strong></a>.\nThis gives us loosely coupled high-level modules that we can translate into code.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_104/bounded_contexts.png\" alt=\"Bounded contexts.\">\n<p>The overarching idea here is thinking about cohesion around functionalities.\nWe want to organize our system so that the cohesion between the components is high.\nBounded contexts, vertical slices, and screaming architecture are complementary concepts.</p>\n<p>Here's a screaming architecture example for this system.\nLet's say the <code>Ticketing</code> module uses <a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure\"><strong>Clean Architecture</strong></a> internally.\nBut we can still organize the system around feature folders and use cases.\nAn alternative approach could be organizing around <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\"><strong>vertical slices</strong></a>, resulting in a less nested folder structure.</p>\n<pre><code class=\"language-powershell\">📁 Modules/\n|__ 📁 Attendance\n    |__ ...\n|__ 📁 Events\n    |__ ...\n|__ 📁 Ticketing\n    |__ 📁 Application\n        |__ 📁 Carts\n            |__ 📁 AddItemToCart\n            |__ 📁 ClearCart\n            |__ 📁 GetCart\n            |__ 📁 RemoveItemFromCart\n        |__ 📁 Orders\n            |__ 📁 SubmitOrder\n            |__ 📁 CancelOrder\n            |__ 📁 GetOrder\n        |__ 📁 Payments\n            |__ 📁 RefundPayment\n        |__ ...\n    |__ 📁 Domain\n        |__ 📁 Customers\n        |__ 📁 Orders\n        |__ 📁 Payments\n        |__ 📁 Tickets\n        |__ ...\n    |__ 📁 infrastructure\n        |__ 📁 Authentication\n        |__ 📁 Customers\n        |__ 📁 Database\n        |__ 📁 Orders\n        |__ 📁 Payments\n        |__ 📁 Tickets\n        |__ ...\n|__ 📁 Users\n    |__ ...\n</code></pre>\n<p>The example above is a small part of the system I built inside of <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<h2>Takeaway</h2>\n<p><strong>Screaming Architecture</strong> isn't just a catchy phrase, it's an approach that can profoundly impact how you build software.\nBy organizing your system around use cases, you align your codebase with the core business domain.\nYour system exists to solve the business domain problems.</p>\n<p>Remember, the goal is to create a system that communicates its purpose through its structure.\nEmbrace a use case-driven approach, break down complex domains into bounded contexts.\nBuild a system that truly &quot;screams&quot; about the problems it solves.</p>\n<p>If you want to explore these powerful ideas further, check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.\nI share my entire framework for building robust applications from the ground up and organizing the system around use cases.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/screaming-architecture",
            "title": "Screaming Architecture",
            "summary": "If you were to glance at the folder structure of your system, could you tell what the system is about?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_104.png",
            "date_modified": "2024-08-24T00:00:00.000Z",
            "date_published": "2024-08-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit",
            "content_html": "<p>Amazon SQS is a fully managed message queue, and Amazon SNS is a pub/sub service that can fan out a copy of every message to multiple SQS queues.\nMassTransit provides messaging abstractions on top of both and provisions the queues, topics, and subscriptions in AWS automatically.\nThis guide covers the core concepts and the full MassTransit setup in .NET.</p>\n<p>Have you ever wondered how large-scale systems handle traffic spikes or maintain performance even when parts of the system are temporarily down?\nThe answer lies in asynchronous messaging.</p>\n<p>Asynchronous messaging is, at its core, about decoupling.\nOur components can operate independently and communicate through a message queue or topic.\nIf one service (component) is temporarily unavailable, the others can continue working.\nThis improves our system's scalability, resilience, and fault tolerance.</p>\n<p>In this article, we'll explore how to use Amazon SQS and SNS for asynchronous messaging in .NET applications.</p>\n<p>We'll also see how MassTransit simplifies the process, enabling you to build robust message-driven systems.</p>\n<p>Let's dive in.</p>\n<h2>What is Amazon SQS?</h2>\n<p><a href=\"https://aws.amazon.com/sqs/\">Amazon Simple Queue Service</a> (SQS) is a fully managed message queueing service.\nIt facilitates the decoupling and scaling of microservices and distributed systems.</p>\n<p>SQS acts as a reliable middleman for asynchronous communication.\nIt enables different components of your architecture to exchange messages without needing to be online or directly connected at the same time.\nMessages are stored in queues and consumed on demand.</p>\n<p>SQS offers two distinct queue types depending on your requirements:</p>\n<ul>\n<li><strong>Standard Queues</strong>: Ideal for high-throughput scenarios.\nStandard Queues provide at-least-once delivery and best-effort ordering.</li>\n<li><strong>FIFO Queues</strong>: Recommended when maintaining message order is required.\nFIFO Queues guarantee <strong>exactly-once processing</strong> and preserve the sequence in which messages are sent.</li>\n</ul>\n<p>Let's say we have two services - <code>Stock</code> and <code>Reporting</code>.\nWhen a user creates a purchase order in the <code>Stock</code> service, we want to notify the <code>Reporting</code> service.</p>\n<p>SQS allows us to create decoupled communication between these services.\nThe <code>Stock</code> service sends a message to an SQS queue, and the <code>Reporting</code> service can poll from the queue to consume messages.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_103/amazon_sqs.png\" alt=\"Amazon SQS.\">\n<p>It's interesting to highlight that SQS uses a polling mechanism for message consumers.\nWhen a consumer polls for new messages, SQS starts a <a href=\"https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html\">visibility timeout</a>.\nSQS doesn't automatically delete the messages.\nInstead, messages are hidden from other consumers until the timeout expires.</p>\n<p>When the consumer successfully processes a message, it's removed from the queue.\nBut if the visibility timeout expires, the message becomes visible and can be delivered again.\nOther consumers can receive this message when polling from SQS.\nThis is why SQS offers at-least-once delivery (for standard queues).\nYou will have to implement <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\"><strong>idempotency in the consumer</strong></a>.</p>\n<h2>Amazon SQS and Competing Consumers</h2>\n<p>Let's introduce another service into our system - the <code>Risk Management</code> service.\nWhen multiple consumers (services) poll an SQS queue, each wants to retrieve and process messages as they become available.\nHowever, once a message is successfully received and processed by one consumer, it's removed from the queue.</p>\n<p>Why is this a problem?</p>\n<p>Other consumers who might have been polling will miss out on that specific message.\nThis is known as <a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/competing-consumers\">competing consumers</a>.</p>\n<p>Let's consider the example of <code>Reporting</code> and <code>Risk Management</code> services polling the same queue.\nIf a new message arrives, only one of these services will &quot;win&quot; the race and retrieve it for processing.\nThe other service won't find that message even if it polls moments later.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_103/amazon_sqs_competing_consumers.png\" alt=\"Amazon SQS and competing consumers.\">\n<p>So, how can we solve this?</p>\n<p>We could introduce a dedicated queue for each service.\nHowever, the producer now needs to publish to multiple queues.\nThis creates a possibility for some (not so) interesting partial failures.\nWhat happens if we successfully publish to one queue but fail to publish to the other?</p>\n<p>You can see how this becomes difficult to scale while maintaining reliability.</p>\n<p>Luckily, there's a solution.</p>\n<h2>Amazon SNS to The Rescue</h2>\n<p><a href=\"https://aws.amazon.com/sns/\">Amazon Simple Notification Service</a> (SNS) is a fully managed pub/sub messaging service.\nIt allows publishers to send messages to multiple subscribers (topics) simultaneously.</p>\n<p>SNS operates on the principle of publishers and subscribers.\nPublishers send messages to an SNS topic, while subscribers express interest in specific topics and receive messages published to those topics.\nThis decoupled architecture allows you to add or remove subscribers without impacting the publisher or other subscribers.</p>\n<p>SNS seamlessly integrates with SQS, allowing you to create a powerful combination where SNS handles the fan-out of messages,\nand SQS queues ensure that each message is processed exclusively by a single consumer (service).</p>\n<p>Instead of sending a message to the queue, the <code>Stock</code> service now publishes to an SNS topic.\nBoth the <code>Reporting</code> and <code>Risk Management</code> services create their own SQS queues and subscribe these queues to the SNS topic.\nWhen a new message is published to the SNS topic, SNS delivers it to both SQS queues.\nEach queue receives its own copy of the message.</p>\n<p>If we want to introduce a new service, we'll create a new SQS queue and subscribe it to the topic.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_103/amazon_sns.png\" alt=\"Amazon SNS.\">\n<h2>MassTransit Integration With SQS and SNS</h2>\n<p>How can we use SNS and SQS from a .NET application?</p>\n<p>You could use the official AWS SDKs.\nThe benefit is you'll have more control over messaging.\nHowever, you will need to write more code to receive and handle messages successfully.</p>\n<p>So, I want to suggest a different approach.</p>\n<p><strong>MassTransit</strong> is one of the most popular messaging libraries in .NET.\nIt provides a set of messaging abstractions on top of the supported message transports.</p>\n<p>I wrote an article about <a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>using MassTransit with RabbitMQ and Azure Service Bus</strong></a>.</p>\n<p>But we'll focus on using MassTransit with SQS and SNS.</p>\n<p>Let's start by installing the NuGet package we'll need:</p>\n<pre><code class=\"language-powershell\">Install-Package MassTransit.AmazonSQS\n</code></pre>\n<p>Next, we'll need to configure MassTransit with our .NET applications.</p>\n<p>Here's the MassTransit configuration for the <code>Stock</code> service:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(configure =&gt;\n{\n    configure.AddConsumer&lt;PurchaseOrderSentConsumer&gt;().Endpoint(e =&gt; e.InstanceId = &quot;stocks&quot;);\n\n    configure.UsingAmazonSqs((context, cfg) =&gt;\n    {\n        cfg.Host(&quot;eu-central-1&quot;, h =&gt;\n        {\n            h.AccessKey(builder.Configuration[&quot;AmazonSqs:AccessKey&quot;]!);\n            h.SecretKey(builder.Configuration[&quot;AmazonSqs:SecretKey&quot;]!);\n\n            h.Scope(&quot;stocks-platform&quot;, scopeTopics: true);\n        });\n\n        cfg.ConfigureEndpoints(context, new KebabCaseEndpointNameFormatter(&quot;stocks-platform-&quot;, false));\n    });\n});\n</code></pre>\n<p>Here are a few things I want to highlight here:</p>\n<ul>\n<li><code>UsingAmazonSqs</code> - Configures SQS (and SNS) as the message transport.</li>\n<li><code>Scope</code> - Adds a prefix to SNS topic names. This helps distinguish topics from other applications and environments.</li>\n<li><code>Endpoint</code> - Sets the <code>InstanceId</code>, which is appended to the endpoint (queue) name.</li>\n<li><code>ConfigureEndpoints</code> - Allows us to specify a prefix for endpoint (queue) names. The idea is the same as the topic name prefix.</li>\n</ul>\n<p>You'll also need to configure an <a href=\"https://masstransit.io/documentation/configuration/transports/amazon-sqs#example-iam-policy\">IAM policy</a>\nthat gives MassTransit the required permissions for AWS resources.</p>\n<p>And with this setup in place, you can use MassTransit to publish messages.\nHere's an endpoint that accepts a purchase order and publishes a <code>PurchaseOrderSent</code> message.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;purchase-orders&quot;, async (PurchaseOrderRequest request, IPublishEndpoint publishEndpoint) =&gt;\n{\n    var purchaseOrder = new PurchaseOrder\n    {\n        Id = Guid.NewGuid(),\n        Ticker = request.Ticker,\n        LimitPrice = request.LimitPrice,\n        Quantity = request.Quantity\n    };\n\n    OrdersDb.Add(purchaseOrder);\n\n    await publishEndpoint.Publish(new PurchaseOrderSent(purchaseOrder.Id));\n\n    return Results.Ok(purchaseOrder);\n});\n</code></pre>\n<p>We will process the purchase order in the <code>PurchaseOrderSentConsumer</code>.\nIf it's successfully processed (filled), we will publish an <code>OrderFilled</code> message.</p>\n<p>The <code>Risk Management</code> service can subscribe to this message using MassTransit.\nThe configuration is almost identical, with the only difference being the endpoint's <code>InstanceId</code>.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(configure =&gt;\n{\n    configure.AddConsumer&lt;OrderFilledConsumer&gt;().Endpoint(e =&gt; e.InstanceId = &quot;risk-management&quot;);\n\n    configure.UsingAmazonSqs((context, cfg) =&gt;\n    {\n        cfg.Host(&quot;eu-central-1&quot;, h =&gt;\n        {\n            h.AccessKey(builder.Configuration[&quot;AmazonSqs:AccessKey&quot;]!);\n            h.SecretKey(builder.Configuration[&quot;AmazonSqs:SecretKey&quot;]!);\n\n            h.Scope(&quot;stocks-platform&quot;, true);\n        });\n\n        cfg.ConfigureEndpoints(context, new KebabCaseEndpointNameFormatter(&quot;stocks-platform-&quot;, false));\n    });\n});\n</code></pre>\n<h2>Broker Topology in AWS</h2>\n<p>MassTransit will automatically create the required queues, topics, and subscriptions in AWS.\nIf needed, you can further configure the SQS and SNS resources.</p>\n<p>Of course, you can also create the required infrastructure in AWS and tell MassTransit to use it.</p>\n<p>But let's keep it simple and allow MassTransit to provision the AWS resources.</p>\n<p>By default, MassTransit creates standard SQS queues.\nHere's what we get in Amazon SQS.</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_103/amazon_sqs_queues.png\" alt=\"Amazon SQS queues created by MassTransit.\">\n</div>\n<p>And here's what we get in Amazon SNS:</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_103/amazon_sns_topics.png\" alt=\"Amazon SNS topics created by MassTransit.\">\n</div>\n<p>MassTransit automatically configures the required subscriptions between the topic and queues.</p>\n<p>And we are ready to start publishing and consuming messages.</p>\n<h2>In Summary</h2>\n<p>Asynchronous communication and decoupling are pivotal in achieving scalability, resilience, and fault tolerance.\n<strong>Amazon SQS</strong> and SNS provide the building blocks of message-driven architectures in the AWS cloud.</p>\n<p>We explored the core concepts of SQS and SNS, understanding how they enable reliable message delivery and fan-out capabilities.</p>\n<p>MassTransit provides an excellent abstraction layer over SQS and SNS, simplifying development.\nWe can focus on solving the business problems and delivering value to our users.</p>\n<p>The combination of Amazon SQS, SNS, and MassTransit gives us robust tools for building modern, event-driven applications.</p>\n<p>Thanks for reading, and I'll see you next week!</p>\n<p><strong>P.S.</strong> You can find the source code for this article in <a href=\"https://github.com/m-jovanovic/aws-tutorials\"><strong>this repository</strong></a>, under the <code>Amazon SQS and SNS</code> folder.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/complete-guide-to-amazon-sqs-and-amazon-sns-with-masstransit",
            "title": "Complete Guide to Amazon SQS and Amazon SNS With MassTransit",
            "summary": "In this article, we'll explore how to use Amazon SQS and SNS for asynchronous messaging in .NET applications.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_103.png",
            "date_modified": "2024-08-17T00:00:00.000Z",
            "date_published": "2024-08-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know",
            "content_html": "<p>The five EF Core features worth knowing are query splitting, bulk updates and deletes with <code>ExecuteUpdate</code> and <code>ExecuteDelete</code>, raw SQL queries for unmapped types, global query filters, and eager loading.\nEach solves a specific problem, from the cartesian explosion to updating many rows in one round trip.\nHere is how they work and when to reach for each one.</p>\n<p>Okay, let's be honest.\nWe all have a million things on our plates, and diving deep into every nook and cranny of EF Core might not be\nat the top of your priority list.</p>\n<p>But here's the deal: EF Core is powerful, and knowing a few key features can save you lots of time and frustration.</p>\n<p>So, I won't bombard you with every single EF Core feature under the sun.</p>\n<p>Instead, I've cherry-picked five essential ones that you really need to know.</p>\n<p>We'll go through:</p>\n<ul>\n<li><strong>Query Splitting</strong> - your database's new best friend</li>\n<li><strong>Bulk Updates and Deletes</strong> - efficiency on steroids</li>\n<li><strong>Raw SQL Queries</strong> - when you need to go rogue</li>\n<li><strong>Query Filters</strong> - keeping things nice and tidy</li>\n<li><strong>Eager Loading</strong> - because lazy isn't so great</li>\n</ul>\n<p>Let's get started!</p>\n<h2>Query Splitting</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting\"><strong>Query splitting</strong></a> is one of those EF Core features that you rarely need.\nUntil one day, you do.\nQuery splitting is helpful in scenarios where you're eager loading multiple collections.\nIt helps us avoid the <a href=\"https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying#avoid-cartesian-explosion-when-loading-related-entities\">cartesian explosion</a> problem.</p>\n<p>Let's say we want to retrieve a department with all its teams and employees.\nWe might write a query like this:</p>\n<pre><code class=\"language-csharp\">Department department =\n    context.Departments\n        .Include(d =&gt; d.Teams)\n        .Include(d =&gt; d.Employees)\n        .Where(d =&gt; d.Id == departmentId)\n        .First();\n</code></pre>\n<p>This translates to a single SQL query with two JOINs.\nHowever, since these <code>JOIN</code> statements are on the same level, the database will return a <em>cross product</em>.\nEach row from <code>Teams</code> will be joined with each row <code>Employees</code>.\nIn that case, the database returns many rows, significantly impacting performance.</p>\n<p>Here's how we can avoid these performance issues with query splitting:</p>\n<pre><code class=\"language-csharp\">Department department =\n    context.Departments\n        .Include(d =&gt; d.Teams)\n        .Include(d =&gt; d.Employees)\n        .Where(d =&gt; d.Id == departmentId)\n        .AsSplitQuery()\n        .First();\n</code></pre>\n<p>With <code>AsSplitQuery</code>, EF Core will execute an additional SQL query for each collection navigation.</p>\n<p>However, be cautious not to overuse query splitting.\nI use split queries when I've <em>measured</em> that they consistently perform better.</p>\n<p>Split queries have more round trips to the database, which might be slower if database latency is high.\nThere is also no consistency guarantee across multiple SQL queries.</p>\n<h2>Bulk Updates and Deletes</h2>\n<p>EF Core 7 added two new APIs for performing <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7\"><strong>bulk updates and deletes</strong></a>,\n<code>ExecuteUpdate</code> and <code>ExecuteDelete</code>.\nThey allow you to efficiently update a large number of rows in one round trip to the database.</p>\n<p>Here's a practical example.</p>\n<p>The company has decided to give a 5% raise to all employees in the &quot;Sales&quot; department.\nWithout bulk updates, we might iterate through each employee and update their salary individually:</p>\n<pre><code class=\"language-csharp\">var salesEmployees = context.Employees\n    .Where(e =&gt; e.Department == &quot;Sales&quot;)\n    .ToList();\n\nforeach (var employee in salesEmployees)\n{\n    employee.Salary *= 1.05m;\n}\n\ncontext.SaveChanges();\n</code></pre>\n<p>This approach involves multiple database roundtrips, which can be inefficient, especially for large datasets.</p>\n<p>We can achieve the same in one roundtrip using <code>ExecuteUpdate</code>:</p>\n<pre><code class=\"language-csharp\">context.Employees\n    .Where(e =&gt; e.Department == &quot;Sales&quot;)\n    .ExecuteUpdate(s =&gt; s.SetProperty(e =&gt; e.Salary, e =&gt; e.Salary * 1.05m));\n</code></pre>\n<p>This executes a single SQL <code>UPDATE</code> statement, directly modifying the salaries in the database without loading entities into memory, giving us improved performance.</p>\n<p>Here's another example.\nLet's say an e-commerce platform wants to delete all shopping carts older than one year.</p>\n<p>Here's how we could do this with <code>ExecuteDelete</code>:</p>\n<pre><code class=\"language-csharp\">context.Carts\n    .Where(o =&gt; o.CreatedOn &lt; DateTime.Now.AddYears(-1))\n    .ExecuteDelete();\n</code></pre>\n<p>This results in a single SQL <code>DELETE</code> statement, directly removing the old shopping carts from the database.</p>\n<p>However, bulk updates bypass the <a href=\"https://milanjovanovic.tech/blog/change-tracker-ef-core\"><strong>EF change tracker</strong></a>.\nThis could be problematic, and I wrote about the <a href=\"https://milanjovanovic.tech/blog/what-you-need-to-know-about-ef-core-bulk-updates\"><strong>caveats of bulk updates in this article</strong></a>.</p>\n<h2>Raw SQL Queries</h2>\n<p>EF Core 8 added a new feature that allows us to query unmapped types with raw SQL.</p>\n<p>Suppose we want to retrieve data from a database view, stored procedure, or a table that doesn't directly correspond to any of our entity classes.</p>\n<p>For example, we want to retrieve a sales summary for each product.\nWith EF Core 8, we can define a simple <code>ProductSummary</code> class representing the structure of the result set and query it directly:</p>\n<pre><code class=\"language-csharp\">public class ProductSummary\n{\n    public int ProductId { get; set; }\n    public string ProductName { get; set; }\n    public decimal TotalSales { get; set; }\n}\n\nvar productSummaries = await context.Database\n    .SqlQuery&lt;ProductSummary&gt;(\n        @$&quot;&quot;&quot;\n        SELECT p.ProductId, p.ProductName, SUM(oi.Quantity * oi.UnitPrice) AS TotalSales\n        FROM Products p\n        JOIN OrderItems oi ON p.ProductId = oi.ProductId\n        WHERE p.CategoryId = {categoryId}\n        GROUP BY p.ProductId, p.ProductName\n        &quot;&quot;&quot;)\n    .ToListAsync();\n</code></pre>\n<p>The <code>SqlQuery</code> method returns an <code>IQueryable</code>, which allows you to compose raw SQL queries with LINQ.\nThis combines the power of raw SQL with the expressiveness of LINQ.</p>\n<p>Remember to use parameterized queries to prevent <strong>SQL injection</strong> vulnerabilities.\nThe <code>SqlQuery</code> method accepts a <code>FormattableString</code>, which means you can safely use an interpolated string.\nEach argument is converted to a SQL parameter.</p>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries\"><strong>raw SQL queries in this article</strong></a>.</p>\n<h2>Query Filters</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core\"><strong>Query filters</strong></a> are like reusable <code>WHERE</code> clauses you can apply to your entities.\nThese filters are automatically added to LINQ queries whenever you retrieve entities of the corresponding type.\nThis saves you from repeatedly writing the same filtering logic in multiple places within your application.</p>\n<p>Query Filters are commonly used for scenarios like:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core\"><strong>Soft Deletes</strong></a>: Filter out records marked as deleted.</li>\n<li><a href=\"https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core\"><strong>Multi-tenancy</strong></a>: Filter data based on the current tenant.</li>\n<li>Row-level security: Restrict access to certain records based on user roles or permissions.</li>\n</ul>\n<p>In a multi-tenant application, you often need to filter data based on the current tenant.\nQuery filters allow us to handle this requirement easily:</p>\n<pre><code class=\"language-csharp\">public class Product\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    // Associate products with tenants\n    public int TenantId { get; set; }\n}\n\nprotected override void OnModelCreating(ModelBuilder modelBuilder)\n{\n    // The current TenantId is set based on the current request/context\n    modelBuilder.Entity&lt;Product&gt;().HasQueryFilter(p =&gt; p.TenantId == _currentTenantId);\n}\n\n// Now, queries automatically filter based on the tenant:\nvar productsForCurrentTenant = context.Products.ToList();\n</code></pre>\n<p>Configuring multiple query filters on the same entity will only apply the last one.\nYou can combine multiple query filters using <code>&amp;&amp;</code> (AND) and <code>||</code> (OR) operators.</p>\n<p>You can use <code>IgnoreQueryFilters</code> to bypass the filters in specific queries when needed.</p>\n<h2>Eager Loading</h2>\n<p>Eager Loading is a feature in EF Core that allows you to load related entities along with your main entity in a single database query.\nBy fetching all necessary data in a single query, you can improve application performance.\nThis is especially true when dealing with complex object graphs or when <a href=\"https://milanjovanovic.tech/blog/lazy-eager-explicit-loading-ef-core\"><strong>lazy loading</strong></a> would result in many small, inefficient queries.</p>\n<p>Here's an example <code>VerifyEmail</code> use case.\nWe want to load an <code>EmailVerificationToken</code> and eagerly load a <code>User</code> with the <code>Include</code> method because we want to modify both entities at the same time.</p>\n<pre><code class=\"language-csharp\">internal sealed class VerifyEmail(AppDbContext context)\n{\n    public async Task&lt;bool&gt; Handle(Guid tokenId)\n    {\n        EmailVerificationToken? token = await context.EmailVerificationTokens\n            .Include(e =&gt; e.User)\n            .FirstOrDefaultAsync(e =&gt; e.Id == tokenId);\n\n        if (token is null || token.ExpiresOnUtc &lt; DateTime.UtcNow || token.User.EmailVerified)\n        {\n            return false;\n        }\n\n        token.User.EmailVerified = true;\n\n        context.EmailVerificationTokens.Remove(token);\n\n        await context.SaveChangesAsync();\n\n        return true;\n    }\n}\n</code></pre>\n<p>EF Core will generate a single SQL query that joins the <code>EmailVerificationToken</code> and <code>User</code> tables, retrieving all the necessary data in one go.</p>\n<p>Eager loading (and query splitting, which we mentioned earlier) isn't a silver bullet.\nConsider using projections if you only need specific properties from related entities to avoid fetching unnecessary data.</p>\n<h2>Summary</h2>\n<p>So, there you have it!\nFive EF Core features that, frankly, you can't afford <em>not</em> to know.\nRemember, mastering EF Core takes time, but these features provide a solid foundation to build upon.</p>\n<p>Another piece of advice is to deeply understand how your database works.\nMastering SQL also allows you to get the most value from EF Core.</p>\n<p>While we focused on five key features, there are many other EF Core features worth exploring:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\"><strong>Optimistic concurrency control</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide\"><strong>Database migrations</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/unleash-ef-core-performance-with-compiled-queries\"><strong>Compiled queries</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core\"><strong>Transactions</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors\"><strong>Interceptors</strong></a></li>\n</ul>\n<p>EF Core is continuously evolving, so keep an eye on the latest updates and releases to stay ahead.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know",
            "title": "5 EF Core Features You Need To Know",
            "summary": "EF Core is powerful, and knowing a few key features can save you lots of time and frustration.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_102.png",
            "date_modified": "2024-08-10T00:00:00.000Z",
            "date_published": "2024-08-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/improving-code-quality-in-csharp-with-static-code-analysis",
            "content_html": "<p>Static code analysis examines your C# code without running it and reports security, performance, and style issues.\n.NET ships with Roslyn analyzers enabled by default for .NET 5 and later, and <code>SonarAnalyzer.CSharp</code> adds more rules.\nYou configure both from <code>Directory.Build.props</code> and tune individual rules in <code>.editorconfig</code>.</p>\n<p>Writing good code is important for any software project.\nIt's also something I deeply care about.\nHowever, it can be hard to spot problems by just reading through everything.</p>\n<p>Luckily, there's a tool that can help: <strong>static code analysis</strong>.</p>\n<p>It's like having an extra pair of eyes automatically checking your code.\nStatic code analysis helps you build secure, maintainable, and high-quality C# code.</p>\n<p>Here's what we are going to cover in this week's newsletter:</p>\n<ul>\n<li>Static code analysis</li>\n<li>Static analysis in .NET</li>\n<li>Finding security risks</li>\n</ul>\n<p>Let's see how static code analysis can help us improve our code quality.</p>\n<h2>What is Static Code Analysis?</h2>\n<p>Static code analysis is a way to examine your code without actually running it.\nIt reports any issues related to security, performance, coding style, or best practices.</p>\n<p>With static code analysis, you can <a href=\"https://en.wikipedia.org/wiki/Shift-left_testing\">&quot;shift left&quot;</a>.\nThis allows you to find and fix issues early in the development process when they're less expensive to solve.</p>\n<p>By writing high-quality code, you'll be able to build systems that are more reliable, scalable, and easier to maintain over time.\nInvesting in code quality will pay dividends in the later stages of any project.</p>\n<p>You can integrate static code analysis into your <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>CI pipeline</strong></a> for a quick feedback loop.\nWe can also pair this with <a href=\"https://milanjovanovic.tech/blog/shift-left-with-architecture-testing-in-dotnet\"><strong>architecture testing</strong></a> to enforce additional coding standards.</p>\n<h2>Static Code Analysis in .NET</h2>\n<p>.NET has built-in Roslyn analyzers that inspect your C# code for code style and quality issues.\nCode analysis is enabled by default if your project targets .NET 5 or later.</p>\n<p>The best way I found to configure static code analysis is using <code>Directory.Build.props</code>.\nIt's an XML file where you can configure common project properties.\nYou can place the <code>Directory.Build.props</code> file in the root folder so it will apply to all projects.</p>\n<p>You can configure the <code>TargetFramework</code>, <code>ImplicitUsings</code>, <code>Nullable</code> (nullable reference types), etc.\nBut what we care about is configuring static code analysis.</p>\n<p>Here are some properties we can configure:</p>\n<ul>\n<li><code>TreatWarningsAsErrors</code> - Treat all warnings as errors.</li>\n<li><code>CodeAnalysisTreatWarningsAsErrors</code> - Treat code quality (CAxxxx) warnings as errors.</li>\n<li><code>EnforceCodeStyleInBuild</code> - Enables code-style analysis (&quot;IDExxxx&quot;) rules.</li>\n<li><code>AnalysisLevel</code> - Specifies which analyzers to enable. The default value is <code>latest</code>.</li>\n<li><code>AnalysisMode</code> - Configures the predefined code analysis configuration.</li>\n</ul>\n<p>We can also install additional NuGet packages to our projects.\n<code>SonarAnalyzer.CSharp</code> contains additional code analyzers to help us write clean, safe, and reliable code.\nThis library comes from the same company that built <a href=\"https://www.sonarsource.com/products/sonarqube/\">SonarQube</a>.</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;\n    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;\n    &lt;Nullable&gt;enable&lt;/Nullable&gt;\n\n    &lt;!-- Configure code analysis. --&gt;\n    &lt;AnalysisLevel&gt;latest&lt;/AnalysisLevel&gt;\n    &lt;AnalysisMode&gt;All&lt;/AnalysisMode&gt;\n    &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;\n    &lt;CodeAnalysisTreatWarningsAsErrors&gt;true&lt;/CodeAnalysisTreatWarningsAsErrors&gt;\n    &lt;EnforceCodeStyleInBuild&gt;true&lt;/EnforceCodeStyleInBuild&gt;\n  &lt;/PropertyGroup&gt;\n\n  &lt;ItemGroup Condition=&quot;'$(MSBuildProjectExtension)' != '.dcproj'&quot;&gt;\n    &lt;PackageReference Include=&quot;SonarAnalyzer.CSharp&quot; Version=&quot;*&quot;&gt;\n      &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt;\n      &lt;IncludeAssets&gt;\n        runtime; build; native; contentfiles; analyzers; buildtransitive\n      &lt;/IncludeAssets&gt;\n    &lt;/PackageReference&gt;\n  &lt;/ItemGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p>The built-in .NET analyzers and the ones from <code>SonarAnalyzer.CSharp</code> can be very helpful.\nBut they can also make a lot of noise with too many build warnings.</p>\n<p>When you encounter code analysis rules that you don't consider helpful, you can turn them off.\nYou can configure individual code analysis rules in the <code>.editorconfig</code> file.</p>\n<pre><code># S125: Sections of code should not be commented out\ndotnet_diagnostic.S125.severity = none\n\n# S1075: URIs should not be hardcoded\ndotnet_diagnostic.S1075.severity = none\n\n# S2094: Classes should not be empty\ndotnet_diagnostic.S2094.severity = none\n\n# S3267: Loops should be simplified with &quot;LINQ&quot; expressions\ndotnet_diagnostic.S3267.severity = none\n</code></pre>\n<h2>Finding (and Fixing) Security Risks</h2>\n<p>Static code analysis can help you detect potential security vulnerabilities in your code.\nHere's an example of a <code>PasswordHasher</code> using only <code>10,000</code> iterations to generate a password hash.\nThe <code>S5344</code> rule, from <code>SonarAnalyzer.CSharp</code>, detects this issue and warns us.\nThe recommended minimal number of iterations is <code>100,000</code>.</p>\n<p>You can navigate to the explanation for <a href=\"https://rules.sonarsource.com/csharp/rspec-5344/\">S5344</a> to learn more:</p>\n<blockquote>\n<p>Weakly hashed password storage poses a significant security risk to software applications.</p>\n</blockquote>\n<p>With <code>TreatWarningsAsErrors</code> turned on, your build will fail until you solve this issue.\nThis reduces the chance of introducing security risks in production.</p>\n<div className=\"bordered\">\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_101/static_code_analysis.png\" alt=\"Example of static code analysis warning.\">\n</div>\n<h2>Conclusion</h2>\n<p>Static code analysis is a powerful tool I include in all my C# projects.\nIt helps me catch problems early, leads to more reliable and secure code, and saves time and effort.\nWhile the initial setup and fine-tuning of rules might take some time, the long-term benefits are undeniable.</p>\n<p>Remember, static code analysis is a tool that complements your existing development practices.</p>\n<p>You can create a robust development process that consistently delivers high-quality software by combining\nstatic code analysis with other techniques like code reviews, <a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>unit testing</strong></a>, and continuous integration.</p>\n<p>Embrace static code analysis.\nYour future self (and your team) will thank you.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<p><strong>P.S.</strong> Here's a sample <a href=\"https://gist.github.com/m-jovanovic/417b7d0a641d7dd7d1972550fba298db\">.editorconfig</a> file you can add to your projects and customize to fit your needs.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/improving-code-quality-in-csharp-with-static-code-analysis",
            "title": "Improving Code Quality in C# With Static Code Analysis",
            "summary": "Static code analysis helps you build secure, maintainable, and high-quality C# code. In this week's newsletter, we'll explore how to integrate it into your…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_101.png",
            "date_modified": "2024-08-03T00:00:00.000Z",
            "date_published": "2024-08-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/simple-messaging-in-dotnet-with-redis-pubsub",
            "content_html": "<p>Redis Pub/Sub sends messages to named channels, and every subscriber listening at that moment receives them.\nNothing is stored, so a message published with no subscribers is discarded, which gives you at-most-once delivery.\nIn .NET you publish and subscribe through an <code>ISubscriber</code> from <code>StackExchange.Redis</code>.</p>\n<p>Redis is a popular choice for <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>caching data</strong></a>, but its capabilities go far beyond that.\nOne of its lesser-known features is Pub/Sub support.\nRedis channels offer an interesting approach for implementing real-time messaging in your .NET applications.\nHowever, as you'll soon see, channels also have some drawbacks.</p>\n<p>In this week's newsletter, we'll explore:</p>\n<ul>\n<li>Basics of Redis channels</li>\n<li>Practical use cases for channels</li>\n<li>Implementing a Pub/Sub example in .NET</li>\n<li>Cache invalidation in distributed systems</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>Redis Channels</h2>\n<p>Redis channels are named communication channels that implement the <a href=\"https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern\">Publish/Subscribe messaging paradigm</a>.\nEach channel is identified by a unique name (e.g., <code>notifications</code>, <code>updates</code>).\nChannels facilitate message delivery from publishers to subscribers.</p>\n<p>Publishers use the <code>PUBLISH</code> command to send messages to a specific channel.\nSubscribers use the <code>SUBSCRIBE</code> command to register interest in receiving messages from a channel.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_100/redis_channel.png\" alt=\"Redis channel with publisher and three subscribers.\">\n<p>Redis channels follow a topic-based publish-subscribe model.\nMultiple publishers can send messages to a channel, and multiple subscribers can receive messages from that channel.</p>\n<p>However, it's crucial to note that Redis channels do not store messages.\nIf there are no subscribers for a channel when a message is published, that message is immediately discarded.</p>\n<p>Redis channels have an <strong>at-most-once delivery</strong> semantics.</p>\n<h2>Practical Use Cases</h2>\n<p>Given that Redis channels operate with <strong>at-most-once delivery</strong> (messages might be lost if there are no subscribers),\nthey are well-suited for scenarios where occasional message loss is acceptable and real-time or near-real-time communication is desired.</p>\n<p>Here are a few possible use cases:</p>\n<ul>\n<li><strong>Social media feeds</strong>: Broadcasting new posts or updates to users.</li>\n<li><strong>Live score updates</strong>: Sending live game scores or sports updates to subscribers.</li>\n<li><strong>Chat applications</strong>: Delivering chat messages in real-time to active participants.</li>\n<li><strong>Collaborative editing</strong>: Propagating changes in collaborative editing environments.</li>\n<li><strong>Distributed cache updates</strong>: Invalidating cache entries across multiple servers when data changes. We'll cover this in detail later in the article.</li>\n</ul>\n<p>Redis channels aren't the best choice for critical data where message loss is unacceptable.\nIn such cases, you should consider a <a href=\"https://milanjovanovic.tech/blog/rabbitmq-vs-kafka-dotnet\"><strong>more reliable messaging system</strong></a>.</p>\n<p>Let's see how we can use Redis channels in .NET.</p>\n<h2>Pub/Sub With Redis Channels</h2>\n<p>We will use the <code>StackExchange.Redis</code> library to send messages with Redis channels.</p>\n<p>Let's start by installing it:</p>\n<pre><code class=\"language-powershell\">Install-Package StackExchange.Redis\n</code></pre>\n<p>You can run <a href=\"https://redis.io/\">Redis</a> locally in a Docker container.\nThe default port is <code>6379</code>.</p>\n<pre><code>docker run -it -p 6379:6379 redis\n</code></pre>\n<p>Here's a simple background service that'll act as our message <code>Producer</code>.</p>\n<p>We're creating a <code>ConnectionMultiplexer</code> by connecting to our Redis instance.\nThis allows us to obtain an <code>ISubscriber</code> that we can use for pub/sub messaging.\nThe <code>ISubscriber</code> will enable us to publish a message to a channel by specifying the channel name.</p>\n<pre><code class=\"language-csharp\">public class Producer(ILogger&lt;Producer&gt; logger) : BackgroundService\n{\n    private static readonly string ConnectionString = &quot;localhost:6379&quot;;\n    private static readonly ConnectionMultiplexer Connection =\n        ConnectionMultiplexer.Connect(ConnectionString);\n\n    private const string Channel = &quot;messages&quot;;\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        var subscriber = Connection.GetSubscriber();\n\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            var message = new Message(Guid.NewGuid(), DateTime.UtcNow);\n\n            var json = JsonSerializer.Serialize(message);\n\n            await subscriber.PublishAsync(Channel, json);\n\n            logger.LogInformation(\n                &quot;Sending message: {Channel} - {@Message}&quot;,\n                message);\n\n            await Task.Delay(5000, stoppingToken);\n        }\n    }\n}\n</code></pre>\n<p>Let's also introduce a separate background service for consuming messages.</p>\n<p>The <code>Consumer</code> connects to the same Redis instance and obtains an <code>ISubscriber</code>.\nThe <code>ISubscriber</code> exposes a <code>SubscribeAsync</code> method that we can use to subscribe to messages from a given channel.\nThis method accepts a callback delegate that we can use to handle the message.</p>\n<pre><code class=\"language-csharp\">public class Consumer(ILogger&lt;Consumer&gt; logger) : BackgroundService\n{\n    private static readonly string ConnectionString = &quot;localhost:6379&quot;;\n    private static readonly ConnectionMultiplexer Connection =\n        ConnectionMultiplexer.Connect(ConnectionString);\n\n    private const string Channel = &quot;messages&quot;;\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        var subscriber = Connection.GetSubscriber();\n\n        await subscriber.SubscribeAsync(Channel, (channel, message) =&gt;\n        {\n            var message = JsonSerializer.Deserialize&lt;Message&gt;(message);\n\n            logger.LogInformation(\n                &quot;Received message: {Channel} - {@Message}&quot;,\n                channel,\n                message);\n        });\n    }\n}\n</code></pre>\n<p>Finally, here's what we get when we run both the <code>Producer</code> and <code>Consumer</code> services:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_100/redis_pub_sub.gif\" alt=\"Redis channels publish/subscribe demo.\">\n<h2>Cache Invalidation in Distributed Systems</h2>\n<p>In a recent project, I tackled a common challenge in distributed systems: keeping the caches in sync.\nWe were using a <a href=\"https://milanjovanovic.tech/blog/fusioncache-multi-level-caching-dotnet\"><strong>two-level caching</strong></a> approach.\nFirst, we had an in-memory cache on each web server for super-fast access.\nSecond, we had a shared Redis cache to avoid hitting our database too often.</p>\n<p>The problem was that when data changed in the database, we needed a way to quickly tell all the web servers to clear their in-memory caches.\nThis is where Redis Pub/Sub came to the rescue.\nWe set up a Redis channel specifically for cache invalidation messages.</p>\n<p>Each application would run a <code>CacheInvalidationBackgroundService</code> that subscribes to messages from the cache invalidation channel.</p>\n<pre><code class=\"language-csharp\">public class CacheInvalidationBackgroundService(\n    IServiceProvider serviceProvider)\n    : BackgroundService\n{\n    public const string Channel = &quot;cache-invalidation&quot;;\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        await subscriber.SubscribeAsync(Channel, (channel, key) =&gt;\n        {\n            var cache = serviceProvider.GetRequiredService&lt;IMemoryCache&gt;();\n\n            cache.Remove(key);\n\n            return Task.CompletedTask;\n        });\n    }\n}\n</code></pre>\n<p>Whenever data changes in the database, we publish a message on this channel with the cache key of the updated data.\nAll the web servers are subscribed to this channel, so they instantly know to remove the old data from their in-memory caches.\nSince the in-memory cache is wiped if the application isn't running, losing cache invalidation messages isn't a problem.\nThis keeps our caches consistent and ensures our users always see the most up-to-date information.</p>\n<h2>In Summary</h2>\n<p>Redis Pub/Sub is not a silver bullet for every messaging need, but its simplicity and speed make it a valuable tool.\nChannels allow us to easily implement communication between loosely coupled components.</p>\n<p>Redis channels have at-most-once delivery semantics, so they're best suited for cases where the occasional dropped message is acceptable.</p>\n<p>I used it to solve the challenge of synchronizing caches across multiple servers.\nThis allowed our system to serve up-to-date data without sacrificing performance.</p>\n<p><strong>P.S.</strong> When you're ready to dive deeper into creating message-driven systems, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.\nI have an entire module dedicated to building reliable distributed messaging and event-driven architecture.</p>\n<p>Good luck out there, and see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/simple-messaging-in-dotnet-with-redis-pubsub",
            "title": "Simple Messaging in .NET With Redis Pub/Sub",
            "summary": "Redis is a popular choice for caching data, but its capabilities go far beyond that. One of its lesser-known features is Pub/Sub support.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_100.png",
            "date_modified": "2024-07-27T00:00:00.000Z",
            "date_published": "2024-07-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing",
            "content_html": "<p>System integration testing verifies how the modules of a single system interact, including the external services they depend on.\nFor a modular monolith, you run the app in memory with <code>WebApplicationFactory</code> and its dependencies in Docker with Testcontainers, then drive real use cases by sending commands and queries.\nBecause modules communicate asynchronously, the test polls until the system becomes consistent.</p>\n<p>Modular monoliths strike a balance between the simplicity of monolithic architecture and the flexibility of microservices.\nBy breaking down applications into cohesive modules, modular monoliths enable easier development and maintenance.\nHowever, they still have a single codebase and deployment unit.</p>\n<p>A critical aspect of the success of a modular monolith is the interaction between its modules.</p>\n<p><strong>System integration testing (SIT)</strong> is an approach to verifying the collaboration of these modules.\nIt involves testing the integration points and communication mechanisms between modules.</p>\n<p>System integration testing allows us to validate the entire system's behavior.\nThis allows us to catch problems early before they become big headaches.</p>\n<p>In this article, we'll learn how to test a modular monolith using system integration testing.</p>\n<p>We'll look at real-world examples and discuss why this testing approach is useful.</p>\n<h2>What Is System Integration Testing?</h2>\n<p>System integration testing (SIT) is an approach to testing the interactions between various modules within a single system.\nThe system could contain many external services, which should also be included during testing.</p>\n<p>System integration testing is the perfect testing approach for modular monoliths.\nIt allows you to mimic the real-world execution of your application (as you'll see later).</p>\n<p>The key benefits of system integration testing are:</p>\n<ul>\n<li><strong>Detecting integration issues</strong>: Discover problems from module integrations that unit testing might miss.</li>\n<li><strong>Validating business logic</strong>: Confirm that end-to-end business processes function correctly across modules.</li>\n<li><strong>Ensuring data integrity</strong>: Verify that data flows accurately and consistently between modules.</li>\n<li><strong>Improving system stability</strong>: Identify and resolve issues early, leading to a more reliable system.</li>\n<li><strong>Building confidence</strong>: Ensure that the system is well-integrated and ready for deployment.</li>\n</ul>\n<h2>Modular Monolith System Example</h2>\n<p>A modular monolith consists of multiple modules, each with a distinct responsibility.\nModules represent high-level components with well-defined boundaries.\nThe modules essentially group together related functionalities (use cases).\nIf you want to learn more about this software architecture, check out this <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>introduction to modular monoliths</strong></a>.</p>\n<p>Let's consider a hypothetical ticketing system that consists of several modules:</p>\n<ul>\n<li><strong>Users Module</strong>: Handles user administration and authentication.</li>\n<li><strong>Events Module</strong>: Manages events, scheduling, and ticket availability.</li>\n<li><strong>Ticketing Module</strong>: Allows users to purchase tickets for various events.</li>\n<li><strong>Attendance Module</strong>: Allows users to check into events using their tickets.</li>\n</ul>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_099/modular_monolith.png\" alt=\"Modular monolith UML diagram.\">\n  <figcaption>\n    Source: <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\">Modular Monolith Architecture</a>\n  </figcaption>\n</figure>\n<p>During system integration testing, we want to ensure that all these modules interact correctly and\nfulfill the business requirements of the ticketing system.</p>\n<h2>Testing Modular Monoliths: User Registration</h2>\n<p>The modules in our ticketing system represent different <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>bounded contexts</strong></a>.\nThe <strong>Users Module</strong> uses the term <code>User</code> as its core entity.\nHowever, the <strong>Ticketing Module</strong> uses the term <code>Customer</code> because it's more aligned with its core responsibility of selling tickets.\nConceptually, both entities represent the same person within our system.</p>\n<p>Here's the scenario we want to test:</p>\n<ul>\n<li>A user registers with our application through the <strong>Users Module</strong></li>\n<li>The <strong>Users Module</strong> publishes an integration event to notify other modules</li>\n<li>The <strong>Ticketing Module</strong> handles the integration event and creates a customer record</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_099/user_registration.png\" alt=\"User registration flow diagram.\">\n<p>If you haven't figured it out by now, our <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>modules communicate asynchronously</strong></a> using messaging.</p>\n<p>Why is this important for our tests?</p>\n<p>There is a time delay from when something occurs in one module until the other modules are notified and handle the <a href=\"https://milanjovanovic.tech/blog/domain-events-vs-integration-events\"><strong>integration events</strong></a>.\nOur system integration tests will have to take into account this delay.</p>\n<p>Here's the test for this scenario:</p>\n<pre><code class=\"language-csharp\">public class RegisterUserTests : BaseIntegrationTest\n{\n    public RegisterUserTests(IntegrationTestWebAppFactory factory)\n        : base(factory)\n    {\n    }\n\n    [Fact]\n    public async Task RegisterUser_Should_PropagateToTicketingModule()\n    {\n        // [Users Module] - Register user\n        var command = new RegisterUserCommand(\n            Faker.Internet.Email(),\n            Faker.Internet.Password(6),\n            Faker.Name.FirstName(),\n            Faker.Name.LastName());\n\n        Result&lt;Guid&gt; userResult = await Sender.Send(command);\n\n        userResult.IsSuccess.Should().BeTrue();\n\n        // [Ticketing Module] - Get customer\n        Result&lt;CustomerResponse&gt; customerResult = await Poller.WaitAsync(\n            TimeSpan.FromSeconds(15),\n            async () =&gt;\n            {\n                var query = new GetCustomerQuery(userResult.Value);\n\n                var customerResult = await Sender.Send(query);\n\n                return customerResult;\n            });\n\n        // Assert\n        customerResult.IsSuccess.Should().BeTrue();\n        customerResult.Value.Should().NotBeNull();\n    }\n}\n</code></pre>\n<p>A lot is going on here, so let's unpack the steps:</p>\n<ul>\n<li>We're using the <code>WebApplicationFactory</code> to run an in-memory application instance</li>\n<li>Any external dependencies run in a Docker container using <a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>Testcontainers</strong></a></li>\n<li>We send the <code>RegisterUserCommand</code> to register a user in the <strong>Users Module</strong></li>\n<li>Then, we have to poll the <strong>Ticketing Module</strong> by sending a <code>GetCustomerQuery</code></li>\n<li>The <code>Poller</code> allows us to wait until the system eventually becomes consistent</li>\n</ul>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet\"><strong>integration testing with Testcontainers in this article</strong></a>.</p>\n<p>System integration tests take longer to execute than integration tests for one module.\nThis is a side effect of testing a bigger slice of the overall system in each test case.</p>\n<p>The <code>Poller</code> class allows you to execute a delegate until you receive a successful result or a timeout occurs.\nYou can customize the timeout duration based on the scenario you are testing.</p>\n<pre><code class=\"language-csharp\">internal static class Poller\n{\n    private static readonly Error Timeout =\n        Error.Failure(&quot;Poller.Timeout&quot;, &quot;The poller has time out&quot;);\n\n    internal static async Task&lt;Result&lt;T&gt;&gt; WaitAsync&lt;T&gt;(\n        TimeSpan timeout,\n        Func&lt;Task&lt;Result&lt;T&gt;&gt;&gt; func)\n    {\n        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));\n\n        DateTime endTimeUtc = DateTime.UtcNow.Add(timeout);\n        while (DateTime.UtcNow &lt; endTimeUtc &amp;&amp;\n               await timer.WaitForNextTickAsync())\n        {\n            Result&lt;T&gt; result = await func();\n\n            if (result.IsSuccess)\n            {\n                return result;\n            }\n        }\n\n        return Result.Failure&lt;T&gt;(Timeout);\n    }\n}\n</code></pre>\n<h2>Testing Modular Monoliths: Adding Ticket to Cart</h2>\n<p>Here's a more complex scenario testing adding a ticket to the customer's cart:</p>\n<ul>\n<li>A user registers with our application through the <strong>Users Module</strong></li>\n<li>The <strong>Users Module</strong> publishes an integration event to notify other modules</li>\n<li>The <strong>Ticketing Module</strong> handles the integration event and creates a customer record</li>\n<li>The <strong>Ticketing Module</strong> creates a dummy event and adds a ticket to the customer's cart</li>\n</ul>\n<p>This scenario mimics a user registering with our system, finding a ticket they want to purchase, and adding it to their cart.\nWe can extend this scenario with more test cases, like ticket purchases.</p>\n<pre><code class=\"language-csharp\">public sealed class AddItemToCartTests : BaseIntegrationTest\n{\n    private const decimal Quantity = 10;\n\n    public AddItemToCartTests(IntegrationTestWebAppFactory factory)\n        : base(factory)\n    {\n    }\n\n    [Fact]\n    public async Task Customer_ShouldBeAbleTo_AddItemToCart()\n    {\n        // [Users Module] - Register user\n        var command = new RegisterUserCommand(\n            Faker.Internet.Email(),\n            Faker.Internet.Password(6),\n            Faker.Name.FirstName(),\n            Faker.Name.LastName());\n\n        Result&lt;Guid&gt; userResult = await Sender.Send(command);\n\n        userResult.IsSuccess.Should().BeTrue();\n\n        // [Ticketing Module] - Get customer\n        Result&lt;CustomerResponse&gt; customerResult = await Poller.WaitAsync(\n            TimeSpan.FromSeconds(15),\n            async () =&gt;\n            {\n                var query = new GetCustomerQuery(userResult.Value);\n                var customerResult = await Sender.Send(query);\n                return customerResult;\n            });\n\n        customerResult.IsSuccess.Should().BeTrue();\n\n        // [Ticketing Module] - Add item to cart\n        CustomerResponse customer = customerResult.Value;\n        var ticketTypeId = Guid.NewGuid();\n\n        await Sender.CreateEventAsync(Guid.NewGuid(), ticketTypeId, Quantity);\n\n        Result result = await Sender.Send(\n            new AddItemToCartCommand(customer.Id, ticketTypeId, Quantity));\n\n        // Assert\n        result.IsSuccess.Should().BeTrue();\n    }\n}\n</code></pre>\n<p>What I like about system integration testing is that the test cases aren't complicated to write.\nThe tests execute the use cases of our system and verify the side effects.\nBuilding your system around <a href=\"https://milanjovanovic.tech/blog/building-your-first-use-case-with-clean-architecture\"><strong>use cases</strong></a> has a few advantages,\nthe main one being that you can focus on the core business logic.\nBut it also makes (integration) testing easier.\nThe use case runs by sending a command or a query.\nWe can execute the use cases in some logical order and check that we get the expected result.</p>\n<h2>In Conclusion</h2>\n<p>System integration testing is a crucial step in creating a successful modular monolith.\nBy testing how different modules work together, you can find and fix problems before they become bigger issues.\nThis makes your software more reliable and saves you time and resources in the long run.</p>\n<p>If you want to dive deeper into building modular systems, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.\nThere's an entire chapter dedicated to testing,\nincluding advanced techniques like system integration testing, using Testcontainers for external services, and automated testing with CI/CD pipelines.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/testing-modular-monoliths-system-integration-testing",
            "title": "Testing Modular Monoliths: System Integration Testing",
            "summary": "System integration testing is the perfect testing approach for modular monoliths. It's an approach to testing the interactions between various modules within a…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_099.png",
            "date_modified": "2024-07-20T00:00:00.000Z",
            "date_published": "2024-07-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-your-first-use-case-with-clean-architecture",
            "content_html": "<p>In Clean Architecture, a use case lives in the Application layer and orchestrates the flow of data to and from domain entities.\nAny external system it needs, like a database or password hasher, is abstracted behind an interface and injected as a dependency.\nThe guiding rule is the Dependency Rule: source code dependencies can only point inward.</p>\n<p>This is a question I often hear: how do I design my use case with Clean Architecture?</p>\n<p>I understand the confusion.\nFiguring out what to place in the Domain, Application, and Infrastructure layer can seem complicated.\nIf that's not enough, we also have to decide what makes up a use case and what should be abstracted away.</p>\n<p>However, things become simpler if we adhere to the main rule in Clean Architecture — <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>the Dependency Rule</strong></a>.\nThis rule states that source code dependencies can only point inwards.</p>\n<p>In this newsletter, we'll explore a practical example of how to apply Clean Architecture principles by building a user registration feature.</p>\n<h2>Clean Architecture</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/why-clean-architecture-is-great-for-complex-projects\"><strong>Clean Architecture</strong></a> has emerged as a guiding principle for crafting maintainable, scalable, and testable applications.\nAt its core, Clean Architecture emphasizes the <strong>separation of concerns</strong> and the <strong>dependency rule</strong>.\nThe dependency rule dictates that dependencies should point inward toward higher-level modules.\nBy following this rule, you create a system where the core business logic of your application is decoupled from external dependencies.\nThis makes it more adaptable to changes and easier to test.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_098/clean_architecture.png\" alt=\"Clean Architecture diagram.\">\n<p>The <a href=\"https://milanjovanovic.tech/blog/domain-layer-clean-architecture\"><strong>Domain layer</strong></a> encapsulates enterprise-wide business rules.\nIt contains domain entities, where an entity is typically an object with methods.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/application-layer-clean-architecture\"><strong>Application layer</strong></a> contains application-specific business rules and encapsulates all of the system's use cases.\nA use case orchestrates the flow of data to and from the domain entities and calls the methods exposed by the entities\nto achieve its goals.</p>\n<p>The Infrastructure and Presentation layers deal with external concerns.\nHere, you will implement any abstractions defined in the inner layers.</p>\n<h2>Describing The Use Case</h2>\n<p>What does it mean for a user to register with our application?\nIt means they reserve an email address (or username) to identify themselves and be able to interact with our system.\nThe user could provide other information, such as a first and last name, an address, and a phone number.</p>\n<p>The first step in building any feature is clearly defining the desired result.</p>\n<p>For user registration, this is what the required operations are:</p>\n<ul>\n<li>The user provides an email and password for registration</li>\n<li>Verify that the email was not reserved previously by an existing account</li>\n<li>Hash the password using some cryptographic hash function (e.g., SHA-256, SHA-512)</li>\n<li>Store the user in the database and (optionally) return an access token to the client</li>\n</ul>\n<p>We could also consider any domain-specific rules or validations that we must enforce.\nA good example is password strength, where we could implement minimum length and complexity requirements.</p>\n<p>Now that we have our requirements let's see how to translate them into a use case.</p>\n<h2>Implementing the Use Case</h2>\n<p>With our requirements in place, we can now define the user registration use case.\nIn <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\"><strong>Clean Architecture</strong></a>, use cases live in the Application layer and orchestrate the interactions between domain entities and external dependencies.</p>\n<p>Let's name our use case <code>RegisterUser</code>.\nIts input will be a <code>RegistrationRequest</code> object containing the user's registration data,\nand its output will be a <code>RegistrationResult</code> object indicating the outcome of the registration attempt.\nNotice that we are using a feature-driven name for the use case.</p>\n<p>What about any external dependencies?\nIf the use case needs to interact with an external system or infrastructure component, we abstract that behind an interface.\nRemember, your application's core business logic should be decoupled from external dependencies.</p>\n<p>The <code>RegisterUser</code> class will use dependency injection to get the necessary dependencies:</p>\n<ul>\n<li><code>IUserRepository</code>: An interface for accessing user data from the database.</li>\n<li><code>IPasswordHasher</code>: An interface for hashing passwords securely.</li>\n</ul>\n<p>The <code>RegisterUser</code> use case will follow these steps:</p>\n<ol>\n<li>Validate input data</li>\n<li>Check for existing <code>User</code></li>\n<li>Hash the password</li>\n<li>Create a new <code>User</code> entity</li>\n<li>Save the <code>User</code> to the database</li>\n<li>Return the result</li>\n</ol>\n<p>Finally, here's the code for our <code>RegisterUser</code> use case:</p>\n<pre><code class=\"language-csharp\">public class RegisterUser(\n    IUserRepository userRepository,\n    IPasswordHasher passwordHasher)\n{\n    public async Task&lt;RegistrationResult&gt; Handle(RegistrationRequest request)\n    {\n        // Validation omitted for brevity\n\n        if (await userRepository.ExistsAsync(request.Email))\n        {\n            return RegistrationResult.EmailNotUnique;\n        }\n\n        var passwordHash = passwordHasher.Hash(request.Password);\n\n        var user = User.Create(\n            request.FirstName, request.LastName, request.Email, passwordHash);\n\n        await userRepository.InsertAsync(user);\n\n        return RegistrationResult.Success;\n    }\n}\n</code></pre>\n<p>A big benefit of this approach is that we can immediately write tests for the <code>RegisterUser</code> use case.\nWe can provide mocks for external dependencies in the tests.\nWe don't need the implementations to exist for this code to compile.\nWith mocks, we can test our business rules and validate our implementation.</p>\n<p><strong>Action step</strong>: How would you extend the <code>RegisterUser</code> use case with more functionality?</p>\n<p>Here are two examples:</p>\n<ul>\n<li>Adding an external identity provider</li>\n<li>Implementing email verification</li>\n</ul>\n<h2>Where Clean Architecture Becomes Muddled</h2>\n<p>By designing our application with Clean Architecture, we produce a system independent of external concerns.\nWe define abstractions in the Application layer and implement them in the Infrastructure layer.\nSo far, so good.</p>\n<p>However, this doesn't mean you can disregard how you integrate with external dependencies.</p>\n<p>In theory, we should be able to &quot;swap&quot; the implementation for any external concern and call it a day.\nIn practice, this couldn't be further from the truth.</p>\n<p>Let me give you two practical examples using the user registration flow.</p>\n<h3>Race Conditions</h3>\n<p>The <code>RegisterUser</code> use case has a race condition.\nConcurrent requests could pass the check for email uniqueness and proceed to register the user.</p>\n<p>We could prevent this race condition by introducing a lock before checking for email uniqueness.\nThat way, only one request will pass the check and proceed to save the user in the database.</p>\n<pre><code class=\"language-csharp\">if (await userRepository.ExistsAsync(request.Email))\n{\n    return RegistrationResult.EmailNotUnique;\n}\n</code></pre>\n<p>However, there is a much more elegant way to solve this.\nWe can introduce a unique index on the <code>Email</code> column in the database.\nA unique index guarantees that only one transaction can write the unique value to the database.\nThe losing transaction will return an error.</p>\n<p>We can handle this exception on the application side and return an appropriate error message to the user.\nThe <code>IUserRepository.InsertAsync</code> method implementation can encapsulate this logic.</p>\n<h3>Changing Hash Functions</h3>\n<p>Let's say we found a security flaw in the hash function used in the <code>IPasswordHasher</code> implementation.\nSo, we spend a few minutes switching to a more secure hash function.\nThe tests for the <code>RegisterUser</code> use case are all green, and everything seems fine.</p>\n<p>The problem? All existing users can no longer log in to the system.</p>\n<p>When an existing user tries to log in with their email and password, the new <code>IPasswordHasher.Hash</code> implementation\nreturns a different password hash from the one stored in the database.</p>\n<p>The correct approach is to phase out the old password hash for existing users.\nWe can add a column in the database that says which hash function produced the hash.\nWe will verify the user's password using the correct hashing function during the login process.</p>\n<p>If the user's password hash still uses the old hashing function, we will verify their password first.\nThen, we can use the password (which we have in memory) to produce a hash using the new hash function.\nWe will store the hash in the database and update the hash function column to the new algorithm.</p>\n<p>Slowly, we will phase out passwords using the old hash function.</p>\n<h2>Conclusion</h2>\n<p>I hope this was helpful in understanding how to apply Clean Architecture principles to a real-world scenario.\nBy focusing on the core business logic first (what it means for a user to register), we can define the requirements for our use case.\nTranslating these requirements into a series of steps within the use case is the easy part.</p>\n<p>But Clean Architecture won't save you from bad engineering.\nIf you don't understand what you are abstracting away, it will become a problem in the long term.</p>\n<p>If you want to go deeper, my flagship course, <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>,\ntakes the guesswork out of structuring your project the right way.\nI share my entire framework for building robust applications from the ground up -\nfrom building a rich domain model to creating use cases to getting your application ready for production.</p>\n<p>And that's all for this week.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-your-first-use-case-with-clean-architecture",
            "title": "Building Your First Use Case With Clean Architecture",
            "summary": "This is a question I often hear: how do I design my use case with Clean Architecture? In this article, we'll explore a practical example of how to apply Clean…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_098.png",
            "date_modified": "2024-07-13T00:00:00.000Z",
            "date_published": "2024-07-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul",
            "content_html": "<p>Service discovery is a pattern that lets services refer to each other by logical names instead of physical IP addresses and ports.\nServices register themselves in a central registry, and clients query the registry to resolve the physical address before sending a request.\nIn .NET, you can implement it with a Consul server and the Steeltoe Discovery library.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>Microservices</strong></a> have revolutionized how we build and scale applications.\nBy breaking down larger systems into smaller, independent services, we gain flexibility, agility, and the ability to adapt to changing requirements quickly.\nHowever, microservices systems are also very dynamic.\nServices can come and go, scale up or down, and even move around within your infrastructure.</p>\n<p>This dynamic nature presents a significant challenge. How do your services find and communicate with each other reliably?</p>\n<p>Hardcoding IP addresses and ports is a recipe for fragility.\nIf a service instance changes location or a new instance spins up, your entire system could grind to a halt.</p>\n<p>Service discovery acts as a central directory for your microservices.\nIt provides a mechanism for services to register themselves and discover the locations of other services.</p>\n<p>In this week's issue, we'll see how to implement service discovery in your .NET microservices with Consul.</p>\n<h2>What is Service Discovery?</h2>\n<p>Service discovery is a pattern that allows developers to use logical names to refer to external services instead of physical IP addresses and ports.\nIt provides a centralized location for services to register themselves.\nClients can query the service registry to find out the service's physical address.\nThis is a common pattern in large-scale distributed systems, such as Netflix and Amazon.</p>\n<p>Here's what the service discovery flow looks like:</p>\n<ol>\n<li>The service will register itself with the service registry</li>\n<li>The client must query the service registry to get the physical address</li>\n<li>The client sends the request to the service using the resolved physical address</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_097/service_discovery_flow.png\" alt=\"Service discovery flow.\">\n<p>The same concept applies when we have multiple services we want to call.\nEach service would register itself with the service registry.\nThe client uses a logical name to reference a service and resolves the physical address from the service registry.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_097/service_discovery_microservices.png\" alt=\"Service discovery with multiple microservices.\">\n<p>The most popular solutions for service discovery are Netflix <a href=\"https://github.com/Netflix/eureka\">Eureka</a> and HashiCorp <a href=\"https://www.consul.io/\">Consul</a>.</p>\n<p>There is also a lightweight solution from Microsoft in the <code>Microsoft.Extensions.ServiceDiscovery</code> library.\nIt uses application settings to resolve the physical addresses for services, so some manual work is still required.\nHowever, you can store service locations in <a href=\"https://azure.microsoft.com/en-us/products/app-configuration\">Azure App Configuration</a> for a centralized service registry.\nI will explore this <strong>service discovery library</strong> in some future articles.</p>\n<p>But now I want to show you how to integrate Consul with .NET applications.</p>\n<h2>Setting Up the Consul Server</h2>\n<p>The simplest way to run the Consul server locally is using a Docker container.\nYou can create a container instance of the <code>hashicorp/consul</code> image.</p>\n<p>Here's an example of configuring the Consul service as part of the <code>docker-compose</code> file:</p>\n<pre><code class=\"language-yml\">consul:\n  image: hashicorp/consul:latest\n  container_name: Consul\n  ports:\n    - '8500:8500'\n</code></pre>\n<p>If you navigate to <code>localhost:8500</code>, you will be greeted by the Consul Dashboard.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_097/consul_dashboard.png\" alt=\"Consul dashboard.\">\n<p>Now, let's see how to register our services with Consul.</p>\n<h2>Service Registration in .NET With Consul</h2>\n<p>We'll use the <a href=\"https://docs.steeltoe.io/api/v3/discovery/\">Steeltoe Discovery</a> library to implement service discovery with Consul.\nThe Consul client implementation lets your applications register services with a Consul server and discover services registered by other applications.</p>\n<p>Let's install the <code>Steeltoe.Discovery.Consul</code> library:</p>\n<pre><code class=\"language-powershell\">Install-Package Steeltoe.Discovery.Consul\n</code></pre>\n<p>We have to configure some services by calling <code>AddServiceDiscovery</code> and explicitly configuring the Consul service discovery client.\nThe alternative is calling <code>AddDiscoveryClient</code> which uses reflection at runtime to determine which service registry is available.</p>\n<pre><code class=\"language-csharp\">using Steeltoe.Discovery.Client;\nusing Steeltoe.Discovery.Consul;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddServiceDiscovery(o =&gt; o.UseConsul());\n\nvar app = builder.Build();\n\napp.Run();\n</code></pre>\n<p>Finally, our service can register with Consul by configuring the logical service name through application settings.\nWhen the application starts, the <code>reporting-service</code> logical name will be added to the Consul service registry.\nConsul will store the respective physical address of this service.</p>\n<pre><code class=\"language-json\">{\n  &quot;Consul&quot;: {\n    &quot;Host&quot;: &quot;localhost&quot;,\n    &quot;Port&quot;: 8500,\n    &quot;Discovery&quot;: {\n      &quot;ServiceName&quot;: &quot;reporting-service&quot;,\n      &quot;Hostname&quot;: &quot;reporting-api&quot;,\n      &quot;Port&quot;: 8080\n    }\n  }\n}\n</code></pre>\n<p>When we start the application and open the Consul dashboard, we should be able to see the <code>reporting-service</code> and its respective physical address.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_097/consul_dashboard_with_service.png\" alt=\"Consul dashboard with registered service.\">\n<h2>Using Service Discovery</h2>\n<p>We can use service discovery when making HTTP calls with an <code>HttpClient</code>.\nService discovery allows us to use a logical name for the service we want to call.\nWhen sending a network request, the service discovery client will replace the logical name with a correct physical address.</p>\n<p>In this example, we're configuring the base address of the <code>ReportingServiceClient</code> <strong>typed client</strong> to <code>http://reporting-service</code>\nand adding service discovery by calling <code>AddServiceDiscovery</code>.</p>\n<p>Load balancing is an optional step, and we can configure it by calling <code>AddRoundRobinLoadBalancer</code> or <code>AddRandomLoadBalancer</code>.\nYou can also configure a custom load balancing strategy by providing an <code>ILoadBalancer</code> implementation.</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddHttpClient&lt;ReportingServiceClient&gt;(client =&gt;\n    {\n        client.BaseAddress = new Uri(&quot;http://reporting-service&quot;);\n    })\n    .AddServiceDiscovery()\n    .AddRoundRobinLoadBalancer();\n</code></pre>\n<p>We can use the <code>ReportingServiceClient</code> typed client like a regular <code>HttpClient</code> to make requests.\nThe service discovery client sends the request to the external service's IP address.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;articles/{id}/report&quot;,\n    async (Guid id, ReportingServiceClient client) =&gt;\n    {\n        var response = await client\n            .GetFromJsonAsync&lt;Response&gt;($&quot;api/reports/article/{id}&quot;);\n\n        return response;\n    });\n</code></pre>\n<h2>Takeaway</h2>\n<p>Service discovery simplifies the management of microservices by automating service registration and discovery.\nThis eliminates the need for manual configuration updates, reducing the risk of errors.</p>\n<p>Services can discover each other's locations on demand, ensuring that communication channels remain open even as the service landscape evolves.\nBy enabling services to discover alternative service instances in case of outages or failures, service discovery enhances the overall resilience of the microservices system.</p>\n<p>Mastering service discovery gives you a powerful tool to build modern distributed applications.</p>\n<p>You can grab the <a href=\"https://github.com/m-jovanovic/service-discovery-consul\">source code for this example here</a>.</p>\n<p>Thanks for reading, and I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/service-discovery-in-microservices-with-net-and-consul",
            "title": "Service Discovery in Microservices With .NET and Consul",
            "summary": "Service discovery is a pattern that allows developers to use logical names to refer to external services, instead of physical IP addresses and ports.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_097.png",
            "date_modified": "2024-07-06T00:00:00.000Z",
            "date_published": "2024-07-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/flexible-pdf-reporting-in-net-using-razor-views",
            "content_html": "<p>To generate PDF reports in .NET, build the report template as a Razor view, render it to an HTML string, and convert that HTML into a PDF with an HTML-to-PDF library.\nYou get full control over formatting and can style the document with modern CSS.</p>\n<p>I'll never forget when I was working on a project that required generating weekly sales reports for a client.\nThe initial solution involved a clunky process of exporting data, manipulating it in spreadsheets, and manually creating PDFs.\nIt was tedious, error-prone, and it sucked up way too much of my time.\nI knew there had to be a better way.</p>\n<p>That's when I discovered the power of combining Razor views with HTML-to-PDF conversion.\nYou have more control over formatting the document.\nYou can use modern CSS to style the HTML markup, which will be applied when exporting to a PDF document.\nIt's also simple to implement in ASP.NET Core.</p>\n<p>Here's what we'll cover:</p>\n<ul>\n<li>Understanding Razor views</li>\n<li>Converting Razor views to HTML</li>\n<li>HTML to PDF conversion in .NET</li>\n<li>Putting it all together with Minimal APIs</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>Razor Views</h2>\n<p>Razor <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/views\">views</a>\nare an HTML template with embedded <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor\">Razor</a> markup.\nRazor allows you to write and execute .NET code inside a web page.\nViews have a special <code>.cshtml</code> file extension.\nThey're commonly used in <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/overview\">ASP.NET Core MVC</a>,\n<a href=\"https://learn.microsoft.com/en-us/aspnet/core/razor-pages\">Razor Pages</a>,\nand <a href=\"https://learn.microsoft.com/en-us/aspnet/core/blazor\">Blazor</a>.</p>\n<p>However, you can define Razor views in a class library or ASP.NET Core Web API project.</p>\n<p>You can use the <code>Model</code> object to pass in data to a Razor view.\nInside the <code>.cshtml</code> file, you'll specify the model type with the <code>@model</code> keyword.\nIn the example below, I'm specifying that the <code>Invoice</code> class is the model for this view.\nYou can access the model instance with the <code>Model</code> property in the view.</p>\n<p>This is the <code>InvoiceReport.cshtml</code> view we'll use to generate a PDF invoice.</p>\n<p>You can write CSS in the Razor view inline or reference a stylesheet.\nI'm using the <a href=\"https://tailwindcss.com/\">Tailwind CSS</a> utility framework, which uses inline CSS.\nI usually delegate this to a front-end engineer on my team so they can stylize the report as needed.</p>\n<pre><code class=\"language-csharp\">@using System.Globalization\n@using HtmlToPdf.Contracts\n\n@model HtmlToPdf.Contracts.Invoice\n\n@{\n    IFormatProvider cultureInfo = CultureInfo.CreateSpecificCulture(&quot;en-US&quot;);\n    var subtotal = Model.LineItems.Sum(li =&gt; li.Price * li.Quantity).ToString(&quot;C&quot;, cultureInfo);\n    var total = Model.LineItems.Sum(li =&gt; li.Price * li.Quantity).ToString(&quot;C&quot;, cultureInfo);\n}\n\n&lt;script src=&quot;https://cdn.tailwindcss.com&quot;&gt;&lt;/script&gt;\n\n&lt;div class=&quot;min-w-7xl flex flex-col bg-gray-200 space-y-4 p-10&quot;&gt;\n    &lt;h1 class=&quot;text-2xl font-semibold&quot;&gt;Invoice #@Model.Number&lt;/h1&gt;\n\n    &lt;p&gt;Issued date: @Model.IssuedDate.ToString(&quot;dd/MM/yyyy&quot;)&lt;/p&gt;\n    &lt;p&gt;Due date: @Model.DueDate.ToString(&quot;dd/MM/yyyy&quot;)&lt;/p&gt;\n\n    &lt;div class=&quot;flex justify-between space-x-4&quot;&gt;\n        &lt;div class=&quot;bg-gray-100 rounded-lg flex flex-col space-y-1 p-4 w-1/2&quot;&gt;\n            &lt;p class=&quot;font-medium&quot;&gt;Seller:&lt;/p&gt;\n            &lt;p&gt;@Model.SellerAddress.CompanyName&lt;/p&gt;\n            &lt;p&gt;@Model.SellerAddress.Street&lt;/p&gt;\n            &lt;p&gt;@Model.SellerAddress.City&lt;/p&gt;\n            &lt;p&gt;@Model.SellerAddress.State&lt;/p&gt;\n            &lt;p&gt;@Model.SellerAddress.Email&lt;/p&gt;\n        &lt;/div&gt;\n        &lt;div class=&quot;bg-gray-100 rounded-lg flex flex-col space-y-1 p-4 w-1/2&quot;&gt;\n            &lt;p class=&quot;font-medium&quot;&gt;Bill to:&lt;/p&gt;\n            &lt;p&gt;@Model.CustomerAddress.CompanyName&lt;/p&gt;\n            &lt;p&gt;@Model.CustomerAddress.Street&lt;/p&gt;\n            &lt;p&gt;@Model.CustomerAddress.City&lt;/p&gt;\n            &lt;p&gt;@Model.CustomerAddress.State&lt;/p&gt;\n            &lt;p&gt;@Model.CustomerAddress.Email&lt;/p&gt;\n        &lt;/div&gt;\n    &lt;/div&gt;\n\n    &lt;div class=&quot;flex flex-col bg-white rounded-lg p-4 space-y-2&quot;&gt;\n        &lt;h2 class=&quot;text-xl font-medium&quot;&gt;Items:&lt;/h2&gt;\n        &lt;div class=&quot;&quot;&gt;\n            &lt;div class=&quot;flex space-x-4 font-medium&quot;&gt;\n                &lt;p class=&quot;w-10&quot;&gt;#&lt;/p&gt;\n                &lt;p class=&quot;w-52&quot;&gt;Name&lt;/p&gt;\n                &lt;p class=&quot;w-20&quot;&gt;Price&lt;/p&gt;\n                &lt;p class=&quot;w-20&quot;&gt;Quantity&lt;/p&gt;\n            &lt;/div&gt;\n\n            @foreach ((int index, LineItem item) in Model.LineItems.Select((li, i) =&gt; (i + 1, li)))\n            {\n                &lt;div class=&quot;flex space-x-4&quot;&gt;\n                    &lt;p class=&quot;w-10&quot;&gt;@index&lt;/p&gt;\n                    &lt;p class=&quot;w-52&quot;&gt;@item.Name&lt;/p&gt;\n                    &lt;p class=&quot;w-20&quot;&gt;@item.Price.ToString(&quot;C&quot;, cultureInfo)&lt;/p&gt;\n                    &lt;p class=&quot;w-20&quot;&gt;@item.Quantity.ToString(&quot;N2&quot;)&lt;/p&gt;\n                &lt;/div&gt;\n            }\n        &lt;/div&gt;\n    &lt;/div&gt;\n\n    &lt;div class=&quot;flex flex-col items-end bg-gray-50 space-y-2 p-4 rounded-lg&quot;&gt;\n        &lt;p&gt;Subtotal: @subtotal&lt;/p&gt;\n        &lt;p&gt;Total: &lt;span class=&quot;font-semibold&quot;&gt;@total&lt;/span&gt;&lt;/p&gt;\n    &lt;/div&gt;\n&lt;/div&gt;\n</code></pre>\n<h2>Converting Razor Views to HTML</h2>\n<p>The next thing we'll need is a way to convert the Razor view into HTML.\nWe can do this with the <a href=\"https://github.com/soundaranbu/Razor.Templating.Core\"><code>Razor.Templating.Core</code></a> library.\nIt provides a simple API to render a <code>.cshtml</code> file into a <code>string</code>.</p>\n<pre><code class=\"language-powershell\">Install-Package Razor.Templating.Core\n</code></pre>\n<p>You can use the <code>RazorTemplateEngine</code> static class to call the <code>RenderAsync</code> method.\nIt accepts the path to the Razor view and the model instance that will be passed to the view.</p>\n<p>Here's what that will look like:</p>\n<pre><code class=\"language-csharp\">Invoice invoice = invoiceFactory.Create();\n\nstring html = await RazorTemplateEngine.RenderAsync(\n    &quot;Views/InvoiceReport.cshtml&quot;,\n    invoice);\n</code></pre>\n<p>Alternatively, you can use the <code>IRazorTemplateEngine</code> instead of the static class.\nIn that case, you must call <code>AddRazorTemplating</code> to register the required services with DI.\nThis is also required if you want to use dependency injection inside the Razor views with <code>@inject</code>.\nIt's recommended that you call <code>AddRazorTemplating</code> after registering all dependencies.</p>\n<pre><code class=\"language-csharp\">services.AddRazorTemplating();\n</code></pre>\n<h2>HTML to PDF conversion</h2>\n<p>Now that we've converted our Razor view into HTML, we can use it to generate a PDF report.\nMany libraries offer this functionality.\nThe library I've used most often is <a href=\"https://ironpdf.com/\">IronPDF</a>.\nIt's a paid library (and well worth it), but I know developers also want free options, so I'll list some alternatives at the end.</p>\n<p>We can use IronPDF's <code>ChromePdfRenderer</code>, which uses an embedded Chrome browser.\nThe renderer exposes the <code>RenderHtmlAsPdf</code> method, which generates a <code>PdfDocument</code>.\nOnce you have the document, you can store it on the file system or export it as binary data.</p>\n<pre><code class=\"language-csharp\">var renderer = new ChromePdfRenderer();\n\nusing var pdfDocument = renderer.RenderHtmlAsPdf(html);\n\npdfDocument.SaveAs($&quot;invoice-{invoice.Number}.pdf&quot;);\n</code></pre>\n<p>If you're looking for free options, check out <a href=\"https://github.com/hardkoded/puppeteer-sharp\">Puppeteer Sharp</a>.\nIt's a .NET port of the <a href=\"https://github.com/puppeteer/puppeteer\">Puppeteer</a> library, which allows you to run a headless Chrome browser.</p>\n<p>Another (conditionally) free option to consider is <a href=\"https://www.nuget.org/packages/NReco.PdfGenerator/\">NReco.PdfGenerator</a>.\nHowever, it's only free for single-server deployments.</p>\n<h2>Putting It All Together</h2>\n<p>Let's use everything we discussed to create a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API endpoint</strong></a> to generate an invoice PDF report and return it as a file response.\nHere's the code snippet:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;invoice-report&quot;, async (InvoiceFactory invoiceFactory) =&gt;\n{\n    Invoice invoice = invoiceFactory.Create();\n\n    var html = await RazorTemplateEngine.RenderAsync(\n        &quot;Views/InvoiceReport.cshtml&quot;,\n        invoice);\n\n    var renderer = new ChromePdfRenderer();\n\n    using var pdfDocument = renderer.RenderHtmlAsPdf(html);\n\n    return Results.File(\n        pdfDocument.BinaryData,\n        &quot;application/pdf&quot;,\n        $&quot;invoice-{invoice.Number}.pdf&quot;);\n});\n</code></pre>\n<p>This is what the generated PDF report looks like:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_096/invoice.png\" alt=\"Invoice report.\">\n<p>You can grab the source code for this sample <a href=\"https://github.com/m-jovanovic/razor-view-html-to-pdf\">here</a>.\nFeel free to try out a different library for HTML to PDF conversion.</p>\n<h2>Summary</h2>\n<p>In this article, we've explored the power of using Razor views for flexible PDF reporting in .NET.\nWe've seen how to create report templates with Razor views, convert them to HTML, and then transform that HTML into beautifully formatted PDF documents.</p>\n<p>Whether you need to generate invoices, sales reports, or any other kind of structured document, this approach offers a simple and customizable solution.</p>\n<p>Here's what you can explore next:</p>\n<ul>\n<li><a href=\"https://youtu.be/XYdcdVWsWos\">PDF Reporting in .NET Using IronPDF and Razor Views</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore\">Creating PDF Reports With QuestPDF</a></li>\n</ul>\n<p>That's all for this week. Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/flexible-pdf-reporting-in-net-using-razor-views",
            "title": "Flexible PDF Reporting in .NET Using Razor Views",
            "summary": "In this article, we'll explore the power of using Razor views for flexible PDF reporting in .NET.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_096.png",
            "date_modified": "2024-06-29T00:00:00.000Z",
            "date_published": "2024-06-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/what-you-need-to-know-about-ef-core-bulk-updates",
            "content_html": "<p>EF Core 7 introduced <code>ExecuteUpdate</code> and <code>ExecuteDelete</code>, which translate a bulk operation directly into a single SQL <code>UPDATE</code> or <code>DELETE</code> statement.\nThat gives them significant performance advantages over loading entities and calling <code>SaveChanges</code>.\nThe important caveat is that they bypass the EF Core Change Tracker, so entities already loaded in memory keep their old values.</p>\n<p>When you're dealing with thousands or even millions of records, efficiency is king.\nThat's where <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7\"><strong>EF Core bulk update</strong></a> capabilities come into play.</p>\n<p>EF Core 7 introduced two powerful new methods, <code>ExecuteUpdate</code> and <code>ExecuteDelete</code>.\nThey're designed to simplify bulk updates in your database.\nBoth methods have their respective async overloads - <code>ExecuteUpdateAsync</code> and <code>ExecuteDeleteAsync</code>.\n<a href=\"https://learn.microsoft.com/en-us/ef/core/saving/execute-insert-update-delete\">EF bulk updates</a> offer significant performance advantages over traditional approaches.</p>\n<p>However, there's an <strong>important caveat</strong>: these bulk operations bypass the EF Core <a href=\"https://milanjovanovic.tech/blog/change-tracker-ef-core\"><strong>Change Tracker</strong></a>.\nThis disconnect can lead to unexpected behavior if you're not aware of it.</p>\n<p>In this week's issue, we'll dive into the details of bulk updates in EF Core.</p>\n<h2>Understanding the EF Core ChangeTracker</h2>\n<p>When you load entities from the database with EF Core, the <code>ChangeTracker</code> starts tracking them.\nAs you update properties, delete entities, or add new ones, the <code>ChangeTracker</code> records these changes.</p>\n<pre><code class=\"language-csharp\">using (var context = new AppDbContext())\n{\n    // Load a product\n    var product = context.Products.FirstOrDefault(p =&gt; p.Id == 1);\n    product.Price = 99.99; // Modify a property\n\n    // At this point, the ChangeTracker knows that 'product' has been modified\n\n    // Add a new product\n    var newProduct = new Product { Name = &quot;New Gadget&quot;, Price = 129.99 };\n    context.Products.Add(newProduct);\n\n    // Delete a product\n    context.Products.Remove(product);\n\n    context.SaveChanges(); // Persist all changes to the database\n}\n</code></pre>\n<p>When you call <code>SaveChanges</code>, EF Core uses the <code>ChangeTracker</code> to determine which SQL commands to execute.\nThis ensures that the database is perfectly synchronized with your modifications.\nThe <code>ChangeTracker</code> acts as a bridge between your in-memory object model and your database.</p>\n<p>If you're already familiar with how EF Core works, this serves mostly as a reminder.</p>\n<h2>Bulk Updates and the ChangeTracker Disconnect</h2>\n<p>Now, let's focus on how <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7\"><strong>bulk updates in EF Core</strong></a>\ninteract with the <code>ChangeTracker</code> - or rather, how they don't interact with it.\nThis design decision might seem counterintuitive, but there's a solid reason behind it: <strong>performance</strong>.</p>\n<p>By directly executing SQL statements against the database, EF Core eliminates the overhead of tracking individual entity modifications.</p>\n<pre><code class=\"language-csharp\">using (var context = new AppDbContext())\n{\n    // Increase price of all electronics by 10%\n    context.Products\n        .Where(p =&gt; p.Category == &quot;Electronics&quot;)\n        .ExecuteUpdate(\n            s =&gt; s.SetProperty(p =&gt; p.Price, p =&gt; p.Price * 1.10));\n\n    // In-memory Product instances with Category == &quot;Electronics&quot;\n    // will STILL have their old price\n}\n</code></pre>\n<p>In this example, we're increasing the price of all products in the <code>Electronics</code> category by 10%.\nThe <code>ExecuteUpdate</code> method efficiently translates the operation into a single SQL <code>UPDATE</code> statement.</p>\n<pre><code class=\"language-sql\">UPDATE [p]\nSET [p].[Price] = [p].[Price] * 1.10\nFROM [Products] as [p];\n</code></pre>\n<p>However, if you inspect the <code>Product</code> instances that EF Core has already loaded into memory, you'll find that their <code>Price</code> properties haven't changed.\nThis might seem surprising if you aren't aware of how bulk updates interact with the change tracker.</p>\n<p>Everything we discussed up to this point also applies to the <code>ExecuteDelete</code> method.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors\"><strong>EF Core interceptors</strong></a> do not trigger for <code>ExecuteUpdate</code> and <code>ExecuteDelete</code> operations.\nIf you need to track or modify bulk update operations, you can create database triggers that fire whenever a relevant table is updated or deleted.\nThis allows you to log details and perform additional actions.</p>\n<h2>The Problem: Maintaining Consistency</h2>\n<p>If <code>ExecuteUpdate</code> completes successfully, the changes are directly committed to the database.\nThis is because bulk operations bypass the <code>ChangeTracker</code> and don't participate in the usual transaction managed by <code>SaveChanges</code>.</p>\n<p>If <code>SaveChanges</code> subsequently fails due to an error (e.g., validation error, database constraint violation, connection issue),\nyou'll be in an inconsistent state.\nThe changes made by <code>ExecuteUpdate</code> are already persisted.\nAny changes made &quot;in memory&quot; are lost.</p>\n<p>The most reliable way to ensure consistency is to wrap both <code>ExecuteUpdate</code> and the operations that lead to <code>SaveChanges</code> in a transaction:</p>\n<pre><code class=\"language-csharp\">using (var context = new AppDbContext())\nusing (var transaction = context.Database.BeginTransaction())\n{\n    try\n    {\n        context.Products\n            .Where(p =&gt; p.Category == &quot;Electronics&quot;)\n            .ExecuteUpdate(\n                s =&gt; s.SetProperty(p =&gt; p.Price, p =&gt; p.Price * 1.10));\n\n        // ... other operations that modify entities\n\n        context.SaveChanges();\n\n        transaction.Commit();\n    }\n    catch (Exception ex)\n    {\n        // You could also let the transaction go out of scope.\n        // This would automatically rollback any changes.\n        transaction.Rollback();\n\n        // Proceed to handle the exception...\n    }\n}\n</code></pre>\n<p>If <code>SaveChanges</code> fails, the transaction will be rolled back, reverting the changes made by both <code>ExecuteUpdate</code> and any other operations within the transaction.\nThis keeps your database in a consistent state.</p>\n<h2>Summary</h2>\n<p>EF Core bulk update features, <code>ExecuteUpdate</code> and <code>ExecuteDelete</code>, are invaluable tools for optimizing performance.\nBy bypassing the <code>ChangeTracker</code> and executing raw SQL directly, they deliver significant speed improvements compared to traditional methods.</p>\n<p>However, it's crucial to be mindful of the potential pitfalls associated with this approach.\nThe disconnect between in-memory entities and the database state can lead to unexpected results if not handled correctly.</p>\n<p>My rule of thumb is to create an explicit <a href=\"https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core\"><strong>database transaction</strong></a> when I want to make additional entity changes.\nWe can be confident that all the changes will persist in the database or none of them will.</p>\n<p>I hope this was helpful, and I'll see you next week.</p>\n<p><strong>P.S.</strong> Get the <a href=\"https://github.com/m-jovanovic/ef-bulk-updates\">source code</a> and try out the examples from this issue.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/what-you-need-to-know-about-ef-core-bulk-updates",
            "title": "What You Need To Know About EF Core Bulk Updates",
            "summary": "EF Core 7 introduced two powerful new methods, ExecuteUpdate and ExecuteDelete. However, there's an important caveat: these bulk operations bypass the EF Core…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_095.png",
            "date_modified": "2024-06-22T00:00:00.000Z",
            "date_published": "2024-06-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/from-transaction-scripts-to-domain-models-a-refactoring-journey",
            "content_html": "<p>A Transaction Script organizes business logic by procedures, where each procedure handles a single request from the presentation layer.\nIt's a simple and effective approach early on, but as business rules accumulate and similar logic gets duplicated across scripts, the code becomes difficult to maintain.\nThe fix is to push the domain logic down into a Domain Model, an object model of the domain that incorporates both behavior and data.</p>\n<p>I once led the development of a fitness tracking app.\nWe started with Transaction Scripts to handle features like workout creation and exercise logging.\nIt was a simple and effective approach for the app's early stages.</p>\n<p>However, as we added more complex features, our business logic became bloated.\nNew business rules intertwined with existing logic, making the code difficult to maintain.\nEach change could introduce unintended consequences.</p>\n<p>We solved our problem by introducing a Domain Model.\nThe domain model shifts the focus from procedures to domain objects.\nOur code became more expressive, easier to reason about, and less prone to errors.</p>\n<p>This experience taught me a valuable lesson, that I want to share in this newsletter.</p>\n<h2>Transaction Script</h2>\n<p>At its core, business applications operate through distinct interactions (transactions) with their users.\nThese transactions can range from simple data retrieval to complex operations involving multiple validations, calculations, and updates to the system's database.</p>\n<p>The Transaction Script pattern provides a simple way to encapsulate the logic behind each transaction.\nIt organizes all the necessary steps, from data access to business rules, into a single, self-contained procedure.</p>\n<blockquote>\n<p>Organizes business logic by procedures where each procedure handles a single request from the presentation.</p>\n</blockquote>\n<p><em>— <a href=\"https://martinfowler.com/eaaCatalog/transactionScript.html\">Transaction Script</a>, Patterns of Enterprise Application Architecture</em></p>\n<p>Here's an example of adding exercises to a workout:</p>\n<pre><code class=\"language-csharp\">internal sealed class AddExercisesCommandHandler(\n    IWorkoutRepository workoutRepository,\n    IUnitOfWork unitOfWork)\n    : ICommandHandler&lt;AddExercisesCommand&gt;\n{\n    public async Task&lt;Result&gt; Handle(\n        AddExercisesCommand request,\n        CancellationToken cancellationToken)\n    {\n        Workout? workout = await workoutRepository.GetByIdAsync(\n            request.WorkoutId,\n            cancellationToken);\n\n        if (workout is null)\n        {\n            return Result.Failure(WorkoutErrors.NotFound(request.WorkoutId));\n        }\n\n        List&lt;Error&gt; errors = [];\n        foreach (ExerciseRequest exerciseDto in request.Exercises)\n        {\n            if (exerciseDto.TargetType == TargetType.Distance &amp;&amp;\n                exerciseDto.DistanceInMeters is null)\n            {\n                errors.Add(ExerciseErrors.MissingDistance);\n\n                continue;\n            }\n\n            if (exerciseDto.TargetType == TargetType.Time &amp;&amp;\n                exerciseDto.DurationInSeconds is null)\n            {\n                errors.Add(ExerciseErrors.MissingDuration);\n\n                continue;\n            }\n\n            var exercise = new Exercise(\n                Guid.NewGuid(),\n                workout.Id,\n                exerciseDto.ExerciseType,\n                exerciseDto.TargetType,\n                exerciseDto.DistanceInMeters,\n                exerciseDto.DurationInSeconds);\n\n            workouts.Exercises.Add(exercise);\n        }\n\n        if (errors.Count != 0)\n        {\n            return Result.Failure(new ValidationError(errors.ToArray()));\n        }\n\n        await unitOfWork.SaveChangesAsync(cancellationToken);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>There isn't much logic here. We're just checking whether the workout exists and whether the exercises are valid.</p>\n<p>What happens when we start to add more logic?</p>\n<p>Let's add another business rule.\nWe must enforce a limit on the number of exercises allowed in a single workout (e.g., no more than 10 exercises).</p>\n<pre><code class=\"language-csharp\">internal sealed class AddExercisesCommandHandler(\n    IWorkoutRepository workoutRepository,\n    IUnitOfWork unitOfWork)\n    : ICommandHandler&lt;AddExercisesCommand&gt;\n{\n    public async Task&lt;Result&gt; Handle(\n        AddExercisesCommand request,\n        CancellationToken cancellationToken)\n    {\n        Workout? workout = await workoutRepository.GetByIdAsync(\n            request.WorkoutId,\n            cancellationToken);\n\n        if (workout is null)\n        {\n            return Result.Failure(WorkoutErrors.NotFound(request.WorkoutId));\n        }\n\n        List&lt;Error&gt; errors = [];\n        foreach (ExerciseRequest exerciseDto in request.Exercises)\n        {\n            if (exerciseDto.TargetType == TargetType.Distance &amp;&amp;\n                exerciseDto.DistanceInMeters is null)\n            {\n                errors.Add(ExerciseErrors.MissingDistance);\n\n                continue;\n            }\n\n            if (exerciseDto.TargetType == TargetType.Time &amp;&amp;\n                exerciseDto.DurationInSeconds is null)\n            {\n                errors.Add(ExerciseErrors.MissingDuration);\n\n                continue;\n            }\n\n            var exercise = new Exercise(\n                Guid.NewGuid(),\n                workout.Id,\n                exerciseDto.ExerciseType,\n                exerciseDto.TargetType,\n                exerciseDto.DistanceInMeters,\n                exerciseDto.DurationInSeconds);\n\n            workouts.Exercises.Add(exercise);\n\n            if (workouts.Exercise.Count &gt; 10)\n            {\n                return Result.Failure(\n                    WorkoutErrors.MaxExercisesReached(workout.Id));\n            }\n        }\n\n        if (errors.Count != 0)\n        {\n            return Result.Failure(new ValidationError(errors.ToArray()));\n        }\n\n        await unitOfWork.SaveChangesAsync(cancellationToken);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>We can continue adding more business logic to the transaction script.\nFor example, we can introduce exercise type restrictions or enforce a specific exercise order.\nYou can imagine how the complexity will keep increasing over time.</p>\n<p>Another concern is code duplication between transaction scripts.\nThis could happen if we need similar logic in multiple transaction scripts.\nYou may be tempted to solve this by calling one transaction script from the other, but this will introduce a different set of problems.</p>\n<p>So, how can we solve these problems?</p>\n<h2>Refactoring to Domain Model</h2>\n<p>What is a domain model?</p>\n<blockquote>\n<p>An object model of the domain that incorporates both behavior and data.</p>\n</blockquote>\n<p><em>— <a href=\"https://martinfowler.com/eaaCatalog/domainModel.html\">Domain Model</a>, Patterns of Enterprise Application Architecture</em></p>\n<p>A domain model lets you encapsulate domain logic (behavior) and state changes (data) inside an object.\nIn <strong>Domain-Driven Design</strong> terminology, we would call this an aggregate.</p>\n<p>An aggregate in DDD is a cluster of objects treated as a single unit for data changes.\nThe aggregate represents a consistency boundary.\nIt helps maintain consistency by ensuring that certain invariants always hold true for the entire aggregate.\nIn our workout example, the <code>Workout</code> class can be treated as an <strong>aggregate root</strong> that encompasses all the exercises within it.</p>\n<p>What does this have to do with a transaction script?</p>\n<p>We can move the domain logic and state changes from the transaction script into the aggregate.\nThis is often called &quot;pushing logic down&quot; into the domain.</p>\n<p>Here's what the domain model will look like when we extract the domain logic:</p>\n<pre><code class=\"language-csharp\">public sealed class Workout\n{\n    private readonly List&lt;Exercise&gt; _exercises = [];\n\n    // Omitting the constructor and other propreties for brevity.\n\n    public Result AddExercises(ExerciseModel[] exercises)\n    {\n        List&lt;Error&gt; errors = [];\n        foreach (var exerciseModel in exercises)\n        {\n            if (exerciseModel.TargetType == TargetType.Distance &amp;&amp;\n                exerciseModel.DistanceInMeters is null)\n            {\n                errors.Add(ExerciseErrors.MissingDistance);\n\n                continue;\n            }\n\n            if (exerciseModel.TargetType == TargetType.Time &amp;&amp;\n                exerciseModel.DurationInSeconds is null)\n            {\n                errors.Add(ExerciseErrors.MissingDuration);\n\n                continue;\n            }\n\n            var exercise = new Exercise(\n                Guid.NewGuid(),\n                workout.Id,\n                exerciseDto.ExerciseType,\n                exerciseDto.TargetType,\n                exerciseDto.DistanceInMeters,\n                exerciseDto.DurationInSeconds);\n\n            workouts.Exercises.Add(exercise);\n\n            if (workouts.Exercise.Count &gt; 10)\n            {\n                return Result.Failure(\n                    WorkoutErrors.MaxExercisesReached(workout.Id));\n            }\n        }\n\n        if (errors.Count != 0)\n        {\n            return Result.Failure(new ValidationError(errors.ToArray()));\n        }\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>With the domain logic inside of the domain model, we can easily share it between transaction scripts.\nTesting the domain model is simpler than testing the transaction script.\nWith a transaction script, we must provide any dependencies (possibly as mocks) for testing.\nHowever, we can test the domain model in isolation.</p>\n<p>The updated transaction script becomes much more straightforward and focused on its primary task:</p>\n<pre><code class=\"language-csharp\">internal sealed class AddExercisesCommandHandler(\n    IWorkoutRepository workoutRepository,\n    IUnitOfWork unitOfWork)\n    : ICommandHandler&lt;AddExercisesCommand&gt;\n{\n    public async Task&lt;Result&gt; Handle(\n        AddExercisesCommand request,\n        CancellationToken cancellationToken)\n    {\n        Workout? workout = await workoutRepository.GetByIdAsync(\n            request.WorkoutId,\n            cancellationToken);\n\n        if (workout is null)\n        {\n            return Result.Failure(WorkoutErrors.NotFound(request.WorkoutId));\n        }\n\n        var exercises = request.Exercises.Select(e =&gt; e.ToModel()).ToArray();\n\n        var result = workout.AddExercises(exercises);\n\n        if (result.IsFailure)\n        {\n            return result;\n        }\n\n        await unitOfWork.SaveChangesAsync(cancellationToken);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Transaction Scripts are a practical starting point for simple applications.\nThey offer a straightforward approach to implementing use cases.\nTransaction scripts are the recommended approach to start building <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices\">vertical slices</a>.\nHowever, transaction scripts can become difficult to maintain as the application grows.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\">Refactoring toward a Domain Model</a> allows you to encapsulate business logic in domain objects.\nThis promotes code reusability and makes your application more adaptable to changes.\nPushing logic down also improves testability and maintainability.</p>\n<p>Should you use a <strong>Transaction Script or a Domain Model</strong>?</p>\n<p>Here's a pragmatic approach you should consider.\nStart with a transaction script, but pay attention to growing complexity.\nWhen you notice a transaction script has too many concerns, consider adding a domain model.\nRemember, the domain model should encapsulate some of the complexity of the domain logic.</p>\n<p>Thanks for reading, and I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/from-transaction-scripts-to-domain-models-a-refactoring-journey",
            "title": "From Transaction Scripts to Domain Models: A Refactoring Journey",
            "summary": "Transaction Scripts organizes business logic by procedures where each procedure handles a single request from the presentation.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_094.png",
            "date_modified": "2024-06-15T00:00:00.000Z",
            "date_published": "2024-06-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance",
            "content_html": "<p>Caching temporarily stores the results of expensive operations in a faster access location, so subsequent requests are served from the cache instead of the source.\nASP.NET Core gives you two primary abstractions for this: <code>IMemoryCache</code> for in-memory caching and <code>IDistributedCache</code> for distributed caches like Redis.\nThe most common strategy is the cache-aside pattern: check the cache first, and on a miss fetch the data from the source and store it for the next request.</p>\n<p>Caching is one of the simplest techniques to significantly improve your application's performance.\nIt's the process of temporarily storing data in a faster access location.\nYou will typically cache the results of expensive operations or frequently accessed data.</p>\n<p>Caching allows subsequent requests for the same data to be served from the cache instead of fetching the data from its source.</p>\n<p>ASP.NET Core offers several types of caches, such as <code>IMemoryCache</code>, <code>IDistributedCache</code>, and the upcoming <code>HybridCache</code> (.NET 9).</p>\n<p>In this newsletter, we will explore how to implement <a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory\">caching in ASP.NET Core</a> applications.</p>\n<h2>How Caching Improves Application Performance</h2>\n<p>Caching improves your application's performance by reducing latency and server load while enhancing scalability and user experience.</p>\n<ul>\n<li><strong>Faster data retrieval</strong>: Cached data can be accessed much faster than retrieving it from the source (like a database or an API).\nCaches are typically stored in memory (RAM).</li>\n<li><strong>Fewer database queries</strong>: Caching frequently accessed data reduces the number of database queries.\nThis reduces the load on the database server.</li>\n<li><strong>Lower CPU usage</strong>: Rendering web pages or processing API responses can consume significant CPU resources.\nCaching the results reduces the need for repetitive CPU-intensive tasks.</li>\n<li><strong>Handling increased traffic</strong>: By reducing the load on backend systems, caching allows your application to handle\nmore concurrent users and requests.</li>\n<li><strong>Distributed caching</strong>: Distributed cache solutions like <a href=\"https://redis.io/\">Redis</a> enable scaling the cache across multiple servers,\nfurther improving performance and resilience.</li>\n</ul>\n<p>In a recent project I worked on, we used Redis to scale to more than 1,000,000 users.\nWe only had one SQL Server instance with a read-replica for reporting.\nThe power of caching, eh?</p>\n<h2>Caching Abstractions in ASP.NET Core</h2>\n<p>ASP.NET Core provides two primary abstractions for working with caches:</p>\n<ul>\n<li><code>IMemoryCache</code>: Stores data in the memory of the web server.\nSimple to use but not suitable for distributed scenarios.</li>\n<li><code>IDistributedCache</code>: Offers a more robust solution for distributed applications.\nIt allows you to store cached data in a distributed cache like Redis.</li>\n</ul>\n<p>We have to register these services with DI to use them.\n<code>AddDistributedMemoryCache</code> will configure the in-memory implementation of <code>IDistributedCache</code>, which isn't distributed.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMemoryCache();\n\nbuilder.Services.AddDistributedMemoryCache();\n</code></pre>\n<p>Here's how you can use the <code>IMemoryCache</code>.\nWe will first check if the cached value is present and return it directly if it's there.\nOtherwise, we must fetch the value from the database and cache it for subsequent requests.</p>\n<pre><code class=\"language-csharp\">app.MapGet(\n    &quot;products/{id}&quot;,\n    (int id, IMemoryCache cache, AppDbContext context) =&gt;\n    {\n        if (!cache.TryGetValue(id, out Product product))\n        {\n            product = context.Products.Find(id);\n\n            var cacheEntryOptions = new MemoryCacheEntryOptions()\n                .SetAbsoluteExpiration(TimeSpan.FromMinutes(10))\n                .SetSlidingExpiration(TimeSpan.FromMinutes(2));\n\n            cache.Set(id, product, cacheEntryOptions);\n        }\n\n        return Results.Ok(product);\n    });\n</code></pre>\n<p>Cache expiration is another important topic to discuss.\nWe want to remove cache entries that aren't used and become stale.\nYou can pass in the <code>MemoryCacheEntryOptions</code>, allowing you to configure cache expiration.\nFor example, we can set the <code>AbsoluteExpiration</code> and <code>SlidingExpiration</code> values to control when the cache entry will expire.</p>\n<h2>Cache-Aside Pattern</h2>\n<p>The <strong>cache-aside pattern</strong> is the most common caching strategy. Here's how it works:</p>\n<ol>\n<li><strong>Check the cache</strong>: Look for the requested data in the cache.</li>\n<li><strong>Fetch from source (if cache miss)</strong>: If the data isn't in the cache, fetch it from the source.</li>\n<li><strong>Update the cache</strong>: Store the fetched data in the cache for subsequent requests.</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_093/cache_aside.png\" alt=\"Cache-aside pattern.\">\n<p>Here's how you can implement the cache-aside pattern as an extension method for <code>IDistributedCache</code>:</p>\n<pre><code class=\"language-csharp\">public static class DistributedCacheExtensions\n{\n    public static DistributedCacheEntryOptions DefaultExpiration =&gt; new()\n    {\n        AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)\n    };\n\n    public static async Task&lt;T&gt; GetOrCreateAsync&lt;T&gt;(\n        this IDistributedCache cache,\n        string key,\n        Func&lt;Task&lt;T&gt;&gt; factory,\n        DistributedCacheEntryOptions? cacheOptions = null)\n    {\n        var cachedData = await cache.GetStringAsync(key);\n\n        if (cachedData is not null)\n        {\n            return JsonSerializer.Deserialize&lt;T&gt;(cachedData);\n        }\n\n        var data = await factory();\n\n        await cache.SetStringAsync(\n            key,\n            JsonSerializer.Serialize(data),\n            cacheOptions ?? DefaultExpiration);\n\n        return data;\n    }\n}\n</code></pre>\n<p>We're using <code>JsonSerializer</code> to manage serialization to and from a JSON string.\nThe <code>SetStringAsync</code> method also accepts a <code>DistributedCacheEntryOptions</code> argument to control cache expiration.</p>\n<p>Here's how we would use this extension method:</p>\n<pre><code class=\"language-csharp\">app.MapGet(\n    &quot;products/{id}&quot;,\n    (int id, IDistributedCache cache, AppDbContext context) =&gt;\n    {\n        var product = cache.GetOrCreateAsync($&quot;products-{id}&quot;, async () =&gt;\n        {\n            var productFromDb = await context.Products.FindAsync(id);\n\n            return productFromDb;\n        });\n\n        return Results.Ok(product);\n    });\n</code></pre>\n<h2>Pros and Cons of In-Memory Caching</h2>\n<p>Pros:</p>\n<ul>\n<li>Extremely fast</li>\n<li>Simple to implement</li>\n<li>No external dependencies</li>\n</ul>\n<p>Cons:</p>\n<ul>\n<li>Cache data is lost if the server restarts</li>\n<li>Limited to the memory (RAM) of a single server</li>\n<li>Cache data is not shared across multiple instances of your application</li>\n</ul>\n<h2>Distributed Caching With Redis</h2>\n<p><a href=\"https://redis.io/\">Redis</a> is a popular in-memory data store often used as a high-performance distributed cache.\nTo use <strong>Redis in your ASP.NET Core application</strong>, you can use the <code>StackExchange.Redis</code> library.</p>\n<p>However, there's also the <code>Microsoft.Extensions.Caching.StackExchangeRedis</code> library,\nallowing you to integrate Redis with <code>IDistributedCache</code>.</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.Extensions.Caching.StackExchangeRedis\n</code></pre>\n<p>Here's how you can configure it with DI by providing a connection string to Redis:</p>\n<pre><code class=\"language-csharp\">string connectionString = builder.Configuration.GetConnectionString(&quot;Redis&quot;);\n\nbuilder.Services.AddStackExchangeRedisCache(options =&gt;\n{\n    options.Configuration = connectionString;\n});\n</code></pre>\n<p>An alternative approach is to register an <code>IConnectionMultiplexer</code> as a service.\nThen, we will use it to provide a function for the <code>ConnectionMultiplexerFactory</code>.</p>\n<pre><code class=\"language-csharp\">string connectionString = builder.Configuration.GetConnectionString(&quot;Redis&quot;);\n\nIConnectionMultiplexer connectionMultiplexer =\n    ConnectionMultiplexer.Connect(connectionString);\n\nbuilder.Services.AddSingleton(connectionMultiplexer);\n\nbuilder.Services.AddStackExchangeRedisCache(options =&gt;\n{\n    options.ConnectionMultiplexerFactory =\n        () =&gt; Task.FromResult(connectionMultiplexer);\n});\n</code></pre>\n<p>Now, when you inject <code>IDistributedCache</code>, it will use Redis under the hood.</p>\n<h2>Cache Stampede and HybridCache</h2>\n<p>The in-memory cache implementations in ASP.NET Core are susceptible to race conditions, which can cause a cache stampede.\nA <a href=\"https://en.wikipedia.org/wiki/Cache_stampede\">cache stampede</a> happens when concurrent requests encounter a cache miss and try to fetch the data from the source.\nThis can overload your application and negate the benefits of caching.</p>\n<p>Locking is one solution for the cache stampede problem.\n.NET offers many options for <a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6\">locking and concurrency control</a>.\nThe most commonly used locking primitives are the <code>lock</code> statement and the <code>Semaphore</code> (or <code>SemaphoreSlim</code>) class.</p>\n<p>Here's how we could use <code>SemaphoreSlim</code> to introduce locking before fetching data:</p>\n<pre><code class=\"language-csharp\">public static class DistributedCacheExtensions\n{\n    private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);\n\n    // Arguments omitted for brevity\n    public static async Task&lt;T&gt; GetOrCreateAsync&lt;T&gt;(...)\n    {\n        // Fetch data from cache, and return if present\n\n        // Cache miss\n        try\n        {\n            await Semaphore.WaitAsync();\n\n            // Check if the data was added to the cache by another request\n\n            // If not, proceed to fetch data and cache it\n            var data = await factory();\n\n            await cache.SetStringAsync(\n                key,\n                JsonSerializer.Serialize(data),\n                cacheOptions ?? DefaultExpiration);\n        }\n        finally\n        {\n            Semaphore.Release();\n        }\n\n        return data;\n    }\n}\n</code></pre>\n<p>The previous implementation has a lock contention issue since all requests have to wait for the semaphore.\nA much better solution would be locking based on the <code>key</code> value.</p>\n<p>.NET 9 introduces a new caching abstraction called <a href=\"https://milanjovanovic.tech/blog/hybrid-cache-in-aspnetcore-new-caching-library\"><code>HybridCache</code></a>, which aims to solve the shortcomings of <code>IDistributedCache</code>.\nLearn more about this in the <a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid\">Hybrid cache documentation</a>.</p>\n<h2>Summary</h2>\n<p>Caching is a powerful technique for improving web application performance.\nASP.NET Core's caching abstractions make it easy to implement various caching strategies.</p>\n<p>We can choose from <code>IMemoryCache</code> for in-memory cache and <code>IDistributedCache</code> for distributed caching.</p>\n<p>Here are a few guidelines to wrap up this week's issue:</p>\n<ul>\n<li>Use <code>IMemoryCache</code> for simple, in-memory caching</li>\n<li>Implement the cache aside pattern to minimize database hits</li>\n<li>Consider Redis as a high-performance distributed cache implementation</li>\n<li>Use <code>IDistributedCache</code> for sharing cached data across multiple applications</li>\n<li>For expensive values you derive from other data, <a href=\"https://milanjovanovic.tech/blog/content-addressed-cache-dotnet\"><strong>content-addressed caching</strong></a> keys the entry on a hash of its inputs and removes invalidation entirely</li>\n</ul>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance",
            "title": "Caching in ASP.NET Core: Improving Application Performance",
            "summary": "Caching is one of the simplest techniques to significantly improve your application's performance.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_093.png",
            "date_modified": "2024-06-08T00:00:00.000Z",
            "date_published": "2024-06-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices",
            "content_html": "<p>Vertical Slice Architecture organizes code by feature instead of by horizontal layers like Presentation, Application, and Domain.\nEach slice is a self-contained unit of functionality that cuts through the whole stack, from the API endpoint down to data access.\nOne way to structure a slice in .NET is a static class per feature that groups a Request, a Response, a Validator, and a Minimal API endpoint holding the use case logic.</p>\n<p>Are you tired of organizing your project across layers?</p>\n<p>Vertical Slice Architecture is a compelling alternative to traditional layered architectures.\n<a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\">VSA</a> flips the script on how we structure code.</p>\n<p>Instead of horizontal layers (Presentation, Application, Domain), VSA organizes code by feature.\nEach feature encompasses everything it needs, from API endpoints to data access.</p>\n<p>In this newsletter, we will explore how you can structure vertical slices in VSA.</p>\n<h2>Understanding Vertical Slices</h2>\n<p>At its core, a vertical slice represents a self-contained unit of functionality.\nIt's a slice through the entire application stack.\nIt encapsulates all the code and components necessary to fulfill a specific feature.</p>\n<p>In traditional layered architectures, code is organized horizontally across the various layers.\nOne feature implementation can be scattered across many layers.\nChanging a feature requires modifying the code in multiple layers.</p>\n<p>VSA addresses this by grouping all the code for a feature into a single slice.</p>\n<p>This shift in perspective brings several advantages:</p>\n<ul>\n<li><strong>Improved cohesion</strong>: Code related to a specific feature resides together, making it easier to understand, modify, and test.</li>\n<li><strong>Reduced complexity</strong>: VSA simplifies your application's mental model by avoiding the need to navigate multiple layers.</li>\n<li><strong>Focus on business logic</strong>: The structure naturally emphasizes the business use case over technical implementation details.</li>\n<li><strong>Easier maintenance</strong>: Changes to a feature are localized within its slice, reducing the risk of unintended side effects.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_092/vertical_slices.png\" alt=\"Vertical slices.\">\n<h2>Implementing Vertical Slice Architecture</h2>\n<p>Here's an example vertical slice representing the <code>CreateProduct</code> feature.\nWe use a static class to represent the feature and group the related types.\nEach feature can have a respective <code>Request</code> and <code>Response</code> class.\nThe use case with the business logic can be in a <a href=\"https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore\">Minimal API endpoint</a>.</p>\n<p>A vertical slice is likely either a command or a query.\nThis approach gives us <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\">CQRS</a> out of the box.</p>\n<pre><code class=\"language-csharp\">public static class CreateProduct\n{\n    public record Request(string Name, decimal Price);\n    public record Response(int Id, string Name, decimal Price);\n\n    public class Endpoint : IEndpoint\n    {\n        public void MapEndpoint(IEndpointRouteBuilder app)\n        {\n            app.MapPost(&quot;products&quot;, Handler).WithTags(&quot;Products&quot;);\n        }\n\n        public static IResult Handler(Request request, AppDbContext context)\n        {\n            var product = new Product\n            {\n                Name = request.Name,\n                Price = request.Price\n            };\n\n            context.Products.Add(product);\n\n            context.SaveChanges();\n\n            return Results.Ok(\n                new Response(product.Id, product.Name, product.Price));\n        }\n    }\n}\n</code></pre>\n<p>I want to mention a few benefits of structuring your application like this.</p>\n<p>The code for the entire <code>CreateProduct</code> feature is tightly grouped within a single file.\nThis makes it extremely easy to locate, understand, and modify everything related to this functionality.\nWe don't need to navigate multiple layers (like controllers, services, repositories, etc.).</p>\n<p>Directly using <code>AppDbContext</code> within the endpoint might tightly couple the slice to your database technology.\nDepending on your project's size and requirements, you could consider abstracting data access (using a repository pattern)\nto make the slice more adaptable to changes in the persistence layer.</p>\n<h2>Introducing Validation in Vertical Slices</h2>\n<p>Vertical slices usually need to solve some <a href=\"https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture\">cross-cutting concerns</a>, one of which is validation.\nValidation is the gatekeeper, preventing invalid or malicious data from entering your system.\nWe can easily implement <a href=\"https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation\">validation with the FluentValidation library</a>.</p>\n<p>Within your slice, you'd define a <code>Validator</code> class that encapsulates the rules specific to your feature's request model.\nIt also supports dependency injection, so we can run complex validations here.</p>\n<pre><code class=\"language-csharp\">public class Validator : AbstractValidator&lt;Request&gt;\n{\n    public Validator()\n    {\n        RuleFor(x =&gt; x.Name).NotEmpty().MaximumLength(100);\n        RuleFor(x =&gt; x.Price).GreaterThanOrEqualTo(0);\n        // ... other rules\n    }\n}\n</code></pre>\n<p>This validator can then be injected into your endpoint using dependency injection, allowing you to perform validation before processing the request.</p>\n<pre><code class=\"language-csharp\">public static class CreateProduct\n{\n    public record Request(string Name, decimal Price);\n    public record Response(int Id, string Name, decimal Price);\n\n    public class Validator : AbstractValidator&lt;Request&gt; // { ... }\n\n    public class Endpoint : IEndpoint\n    {\n        public void MapEndpoint(IEndpointRouteBuilder app)\n        {\n            app.MapPost(&quot;products&quot;, Handler).WithTags(&quot;Products&quot;);\n        }\n\n        public static IResult Handler(\n            Request request,\n            IValidator&lt;Request&gt; validator,\n            AppDbContext context)\n        {\n            var validationResult = await validator.Validate(request);\n            if (!validationResult.IsValid)\n            {\n                return Results.BadRequest(validationResult.Errors);\n            }\n\n            // ... (Create product and return response)\n        }\n    }\n}\n</code></pre>\n<h2>Handling Complex Features and Shared Logic</h2>\n<p>The previous examples were simple.\nBut what do we do with complex features and shared logic?</p>\n<p>VSA excels at managing self-contained features.\nHowever, real-world applications often involve complex interactions and shared logic.</p>\n<p>Here are a few strategies you can consider to address this:</p>\n<ul>\n<li><strong>Decomposition</strong>: Break down complex features into smaller, more manageable vertical slices.\nEach slice should represent a cohesive piece of the overall feature.</li>\n<li><strong>Refactoring</strong>: When a vertical slice becomes difficult to maintain, you can apply some refactoring techniques.\nThe most common ones I use are <code>Extract method </code> and <code>Extract class</code>.</li>\n<li><strong>Extract shared logic</strong>: Identify common logic that's used across multiple features.\nCreate a separate class (or extension method) to reference it from your vertical slices as needed.</li>\n<li><strong>Push logic down</strong>: Write vertical slices using procedural code, like a <a href=\"https://martinfowler.com/eaaCatalog/transactionScript.html\">Transaction Script</a>.\nThen, you can identify parts of the business logic that naturally belong to the domain entities.</li>\n</ul>\n<p>You and your team will need to understand code smells and refactorings to make the most of VSA.</p>\n<h2>Summary</h2>\n<p>Vertical Slice Architecture is more than just a way to structure your code.\nBy focusing on features, VSA allows you to create cohesive and maintainable applications.\nVertical slices are self-contained, making <a href=\"https://milanjovanovic.tech/blog/testing-vertical-slices-dotnet\"><strong>unit and integration testing</strong></a> more straightforward.</p>\n<p>VSA brings benefits in terms of code organization and development speed, making it a valuable tool in your toolbox.\nCode is grouped by feature, making it easier to locate and understand.\nThe structure aligns with the way business users think about features.\nChanges are localized, reducing the risk of regressions and enabling faster iterations.</p>\n<p>Consider embracing <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\">Vertical Slice Architecture</a> in your next project.\nIt's a big mindset shift from <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\">Clean Architecture</a>.\nHowever, they both have their place and even share similar ideas.</p>\n<p>That's all for this week. Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/vertical-slice-architecture-structuring-vertical-slices",
            "title": "Vertical Slice Architecture: Structuring Vertical Slices",
            "summary": "Are you tired of organizing your project across layers? Vertical Slice Architecture is a compelling alternative to traditional layered architectures.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_092.png",
            "date_modified": "2024-06-01T00:00:00.000Z",
            "date_published": "2024-06-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/shift-left-with-architecture-testing-in-dotnet",
            "content_html": "<p>Architecture tests are automated tests that check whether your code follows your architectural rules, such as which layers or modules may depend on each other.\nIn .NET you write them with a library such as NetArchTest or ArchUnitNET and run them in CI.\nA violation fails the build, so you catch the problem while it is cheap to fix.</p>\n<p>Picture this: You're part of a team building a shiny new .NET application.\nYou've carefully chosen your software architecture.\nIt could be microservices, a <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>modular monolith</strong></a>, or something else entirely.\nYou've decided which database you will use and all the other tools you need.\nEveryone's excited, the code is flowing, and features are getting shipped.</p>\n<p>Fast forward a few months (or years), and things might look different.</p>\n<p>The codebase has grown, and new features have been added.\nMaybe your team has even changed, with new developers coming on board.\nAdding new features becomes a pain, and bugs are popping up left and right.</p>\n<p>And slowly but surely, the neat architecture you started with has turned into a <a href=\"https://deviq.com/antipatterns/big-ball-of-mud\">big ball of mud</a>.\nWhat went wrong? And more importantly, what can we do about it?</p>\n<p>Today, I want to show you how architecture testing can prevent this problem.</p>\n<h2>Technical Debt</h2>\n<p>Technical debt is the consequence of prioritizing development speed over well-designed code.\nIt happens when teams cut corners to meet deadlines, make quick fixes, or don't understand the architecture clearly.</p>\n<p>Each shortcut or hack adds to the pile, making the code harder to understand, change, and maintain.\nBut why do developers take these shortcuts in the first place?</p>\n<p>Don't developers care about keeping the code clean?</p>\n<p>Well, the truth is, most developers do care.\nIf you're reading this newsletter, odds are you also care.\nBut, developers are often under pressure to deliver features quickly.\nSometimes, the quickest way to do that is to take a shortcut.</p>\n<p>Plus, not everyone has a deep understanding of software architecture, or they might disagree on what the &quot;right&quot; architecture is.\nAnd let's be honest: some developers want to get their code working and move on to the next thing.</p>\n<h2>Architecture Testing</h2>\n<p>Luckily, there's a way to enforce software architecture on your project before things get out of hand.\nIt's called <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>architecture testing</strong></a>.\nThese are automated tests that check whether your code follows the architectural rules you've set up.</p>\n<p>With architecture testing, you can <a href=\"https://en.wikipedia.org/wiki/Shift-left_testing\">&quot;shift left&quot;</a>.\nThis enables you to find and fix problems early in the development process when they're much easier and cheaper to deal with.</p>\n<p>Think of it like a safety net for your software architecture and design rules.\nIf someone accidentally breaks a rule, the test will catch it and alert you.\nBonus points if you integrate architecture testing into your <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>CI pipeline</strong></a>.</p>\n<p>There are a few libraries you can use for architecture testing.\nI prefer working with the <a href=\"https://github.com/BenMorris/NetArchTest\">NetArchTest</a> library, which I'll use for the examples.</p>\n<p>You can check out this article to learn the <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>fundamentals of architecture testing</strong></a>.</p>\n<p>Let's see how to write some architecture tests.</p>\n<h2>Architecture Testing: Modular Monolith</h2>\n<p>You built an application using the <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith architecture</strong></a>.\nBut how can you maintain the constraints between the modules?</p>\n<ul>\n<li>Modules aren't allowed to reference each other</li>\n<li>Modules can only call the public API of other modules</li>\n</ul>\n<p>Here's an architecture test that enforces these module constraints.\nThe <code>Ticketing</code> module is not allowed to reference the other modules directly.\nHowever, it can reference the public API of other modules (<a href=\"https://milanjovanovic.tech/blog/domain-events-vs-integration-events\"><strong>integration events</strong></a> in this example).\nThe entry point is the <code>Types</code> class, which exposes a fluent API to build the rules you want to enforce.\nNetArchTest allows us to enforce the direction of dependencies between modules.</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void TicketingModule_ShouldNotHaveDependencyOn_AnyOtherModule()\n{\n    string[] otherModules = [\n        UsersNamespace,\n        EventsNamespace,\n        AttendanceNamespace];\n\n    string[] integrationEventsModules = [\n        UsersIntegrationEventsNamespace,\n        EventsIntegrationEventsNamespace,\n        AttendanceIntegrationEventsNamespace];\n\n    List&lt;Assembly&gt; ticketingAssemblies =\n    [\n        typeof(Order).Assembly,\n        Modules.Ticketing.Application.AssemblyReference.Assembly,\n        Modules.Ticketing.Presentation.AssemblyReference.Assembly,\n        typeof(TicketingModule).Assembly\n    ];\n\n    Types.InAssemblies(ticketingAssemblies)\n        .That()\n        .DoNotHaveDependencyOnAny(integrationEventsModules)\n        .Should()\n        .NotHaveDependencyOnAny(otherModules)\n        .GetResult()\n        .ShouldBeSuccessful();\n}\n</code></pre>\n<p>If you want to learn how to build robust and scalable systems using this architectural approach, check out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<h2>Architecture Testing: Clean Architecture</h2>\n<p>We can also write architecture tests for <a href=\"https://milanjovanovic.tech/blog/why-clean-architecture-is-great-for-complex-projects\"><strong>Clean Architecture</strong></a>.\nThe inner layers aren't allowed to reference the outer layers.\nInstead, the inner layers define abstractions and the outer layers implement these abstractions.</p>\n<p>For example, the <code>Domain</code> layer isn't allowed to reference the <code>Application</code> layer.\nHere's an architecture test enforcing this rule:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void DomainLayer_ShouldNotHaveDependencyOn_ApplicationLayer()\n{\n    Types.InAssembly(DomainAssembly)\n        .Should()\n        .NotHaveDependencyOn(ApplicationAssembly.GetName().Name)\n        .GetResult()\n        .ShouldBeSuccessful();\n}\n</code></pre>\n<p>It's also simple introduce a rule that the <code>Application</code> layer isn't allowed to reference the <code>Infrastructure</code> layer.\nThe architecture test will fail whenever someone in the team breaks the <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>dependency rule</strong></a>.</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void ApplicationLayer_ShouldNotHaveDependencyOn_InfrastructureLayer()\n{\n    Types.InAssembly(ApplicationAssembly)\n        .Should()\n        .NotHaveDependencyOn(InfrastructureAssembly.GetName().Name)\n        .GetResult()\n        .ShouldBeSuccessful();\n}\n</code></pre>\n<p>We can introduce more architecture tests for the <code>Infrastructure</code> and <code>Presentation</code> layers, if needed.</p>\n<p>Ready to learn more about building production-ready applications using this architectural approach?\nYou should check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong></a>.</p>\n<h2>Architecture Testing: Design Rules</h2>\n<p>Architecture testing is also useful for enforcing design rules in your code.\nIf your team has coding standards everyone should follow, architecture testing can help you enforce them.</p>\n<p>For example, we want to ensure that all domain events are sealed types.\nYou can use the <code>BeSealed</code> method to enforce a design rule that types implementing <code>IDomainEvent</code> or <code>DomainEvent</code> should be sealed.</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void DomainEvents_Should_BeSealed()\n{\n    Types.InAssembly(DomainAssembly)\n        .That()\n        .ImplementInterface(typeof(IDomainEvent))\n        .Or()\n        .Inherit(typeof(DomainEvent))\n        .Should()\n        .BeSealed()\n        .GetResult()\n        .ShouldBeSuccessful();\n}\n</code></pre>\n<p>An interesting design rule could be requiring all domain entities not to have a public constructor.\nInstead, you would create an <code>Entity</code> instance through a static factory method.\nThis approach improves the encapsulation of your <code>Entity</code>.</p>\n<p>Here's an architecture test enforcing this design rule:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void Entities_ShouldOnlyHave_PrivateConstructors()\n{\n    IEnumerable&lt;Type&gt; entityTypes = Types.InAssembly(DomainAssembly)\n        .That()\n        .Inherit(typeof(Entity))\n        .GetTypes();\n\n    var failingTypes = new List&lt;Type&gt;();\n    foreach (Type entityType in entityTypes)\n    {\n        ConstructorInfo[] constructors = entityType\n            .GetConstructors(BindingFlags.Public | BindingFlags.Instance);\n\n        if (constructors.Any())\n        {\n            failingTypes.Add(entityType);\n        }\n    }\n\n    failingTypes.Should().BeEmpty();\n}\n</code></pre>\n<p>Another thing you can do with architecture tests is enforce naming conventions in your code.\nHere's an example of requiring all command handlers to have a name ending with <code>CommandHandler</code>:</p>\n<pre><code class=\"language-csharp\">[Fact]\npublic void CommandHandler_ShouldHave_NameEndingWith_CommandHandler()\n{\n    Types.InAssembly(ApplicationAssembly)\n        .That()\n        .ImplementInterface(typeof(ICommandHandler&lt;&gt;))\n        .Or()\n        .ImplementInterface(typeof(ICommandHandler&lt;,&gt;))\n        .Should()\n        .HaveNameEndingWith(&quot;CommandHandler&quot;)\n        .GetResult()\n        .ShouldBeSuccessful();\n}\n</code></pre>\n<h2>Summary</h2>\n<p>Even the most well-planned software projects decay because of technical debt.\nMost developers have good intentions.\nHowever, time pressure, misunderstandings, and resistance to rules all contribute to this problem.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>Architecture testing</strong></a> acts as a safeguard.\nIt prevents your codebase from turning into a big ball of mud.\nBy catching architectural violations early on, you can shift left.\nShort feedback loops avoid costly rework and improve developer productivity.\nIt also ensures the long-term health of your project.</p>\n<p>A few key takeaways:</p>\n<ul>\n<li><strong>Technical debt is inevitable</strong>: It slows down development, introduces bugs, and frustrates developers.</li>\n<li><strong>Architecture testing is your safety net</strong>: It helps you catch architectural violations before they become problematic.</li>\n<li><strong>Start small and iterate</strong>: You don't have to test everything at once. Focus on the most critical rules first.</li>\n<li><strong>Make it part of your workflow</strong>: Integrate architecture tests into your CI/CD pipeline so they run automatically.</li>\n</ul>\n<p><strong>Action point</strong>: Start by exploring popular .NET architecture testing libraries like <a href=\"https://github.com/TNG/ArchUnitNET\">ArchUnitNET</a>\nor <a href=\"https://github.com/BenMorris/NetArchTest\">NetArchTest</a>.\nExperiment with writing tests for common architectural rules and gradually integrate them into your development workflow.\nI walked through the ArchUnitNET setup, the rules worth writing first, and the ways these tests quietly pass when they shouldn't in <a href=\"https://milanjovanovic.tech/blog/architecture-fitness-functions-archunitnet\"><strong>architecture fitness functions with ArchUnitNET</strong></a>.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/shift-left-with-architecture-testing-in-dotnet",
            "title": "Shift Left With Architecture Testing in .NET",
            "summary": "In this newsletter, we'll explore how architecture testing can safeguard our project's architecture.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_091.png",
            "date_modified": "2024-05-25T00:00:00.000Z",
            "date_published": "2024-05-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide",
            "content_html": "<p>EF Core migrations version your database schema in code instead of hand-written SQL scripts.\nEach migration captures one change to the data model and contains <code>Up</code> and <code>Down</code> methods, so you can apply or revert it.\nYou can apply migrations with SQL scripts, the CLI, a migration bundle, or from code at startup.</p>\n<p>Managing database schemas as your applications grow can quickly become a headache.\nManual changes are error-prone and time-consuming.\nThis can easily lead to inconsistencies between development and production environments.\nI've seen these issues firsthand on countless projects, and it's not pretty.\nHow can we do better?</p>\n<p>Enter Entity Framework (EF) Migrations, a powerful tool that lets you version your database schemas.</p>\n<p>Imagine this: Instead of writing SQL scripts, you define your changes in code.\nNeed to add a column?\nRename a table?\nNo problem - EF Migrations has you covered.\nIt tracks every modification to the data model.\nYou can review, test, and apply changes confidently, even across different environments.</p>\n<p>In this newsletter, we'll break down the essentials of EF Migrations:</p>\n<ul>\n<li><strong>Creating Migrations</strong>: Defining and generating migrations that capture your schema changes.</li>\n<li><strong>Migration SQL Scripts</strong>: Understanding the SQL generated by your migrations and how to use it.</li>\n<li><strong>Applying Migrations</strong>: Different ways to apply migrations to your database.</li>\n<li><strong>Migration Tools</strong>: Exploring additional tools and frameworks for managing database migration.</li>\n<li><strong>EF Migration Best Practices:</strong>: I'll share my recommendations from using EF for years.</li>\n</ul>\n<p>We have many examples to cover, so let's dive in.</p>\n<h2>Creating Migrations</h2>\n<p>If you're completely new to EF migrations, I recommend checking out the <a href=\"https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations\">EF migrations docs</a>\nto grasp the fundamentals.\nMoving forward, I'll assume you have some prior knowledge of EF Core.</p>\n<p>We'll need an entity and a database context before we can create migrations with EF.</p>\n<p>Let's define a simple <code>Product</code> entity:</p>\n<pre><code class=\"language-csharp\">public class Product\n{\n    public int Id { get; set; }\n\n    public string Name { get; set; } = string.Empty;\n\n    public string? Description { get; set; }\n\n    public decimal Price { get; set; }\n}\n</code></pre>\n<p>We will also need a <code>DbContext</code> implementation, so let's define the <code>AppDbContext</code> class.\nIn the <code>OnModelCreating</code> method, we're going to configure the <code>Product</code> entity.</p>\n<pre><code class=\"language-csharp\">public class AppDbContext : DbContext\n{\n    public AppDbContext(DbContextOptions&lt;AppDbContext&gt; options)\n        : base(options)\n    {\n    }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity&lt;Product&gt;(builder =&gt;\n        {\n            builder.ToTable(&quot;Products&quot;, tableBuilder =&gt;\n            {\n                tableBuilder.HasCheckConstraint(\n                    &quot;CK_Price_NotNegative&quot;,\n                    sql: $&quot;{nameof(Product.Price)} &gt; 0&quot;);\n            });\n\n            builder.HasKey(p =&gt; p.Id);\n\n            builder.Property(p =&gt; p.Name).HasMaxLength(100);\n\n            builder.Property(p =&gt; p.Description).HasMaxLength(1000);\n\n            builder.Property(p =&gt; p.Price).HasPrecision(18, 2);\n\n            builder.HasIndex(p =&gt; p.Name).IsUnique();\n        });\n    }\n}\n</code></pre>\n<p>Let's break down a few of the methods we're using:</p>\n<ul>\n<li><code>ToTable</code> - Configures the table name for the specific entity.\nIt also allows us to provide <code>TableBuilder</code> delegate.\nWe can use it to configure a check constraint using <code>HasCheckConstraint</code>.</li>\n<li><code>HasKey</code> - Configures the table's primary key.\nEF will also pick up the <code>Id</code> property by convention, so this step is optional.</li>\n<li><code>HasProperty</code> - Represents the entry point for configuring individual properties of the entity.</li>\n<li><code>HasIndex</code> - Defines an index on the specified property (or properties).\nWe can also declare that the index should be unique by calling <code>IsUnique</code>.</li>\n</ul>\n<p>We're now ready to create our first migration.\nI'm going to use the PowerShell syntax:</p>\n<pre><code>Add-Migration Create_Database\n</code></pre>\n<p>This will create the first database migration called <code>Create_Database</code>.\nThe migration will apply the configuration we defined in the <code>OnModelCreating</code> method.\nIt contains the <code>Up</code> and <code>Down</code> methods, allowing us to apply or revert changes to the database.</p>\n<p>Note that some operations are destructive (like removing a column) and can't be easily reverted.\nIt's up to you to examine the generated migration and prevent any possible data loss.</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore.Migrations;\nusing Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;\n\npublic partial class Create_Database : Migration\n{\n    protected override void Up(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.CreateTable(\n            name: &quot;Products&quot;,\n            columns: table =&gt; new\n            {\n                Id = table.Column&lt;int&gt;(type: &quot;integer&quot;, nullable: false)\n                    .Annotation(&quot;Npgsql:ValueGenerationStrategy&quot;, NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),\n                Name = table.Column&lt;string&gt;(type: &quot;character varying(100)&quot;, maxLength: 100, nullable: false),\n                Description = table.Column&lt;string&gt;(type: &quot;character varying(1000)&quot;, maxLength: 1000, nullable: true),\n                Price = table.Column&lt;decimal&gt;(type: &quot;numeric(18,2)&quot;, precision: 18, scale: 2, nullable: false)\n            },\n            constraints: table =&gt;\n            {\n                table.PrimaryKey(&quot;PK_Products&quot;, x =&gt; x.Id);\n                table.CheckConstraint(&quot;CK_Price_NotNegative&quot;, &quot;Price &gt; 0&quot;);\n            });\n\n        migrationBuilder.CreateIndex(\n            name: &quot;IX_Products_Name&quot;,\n            table: &quot;Products&quot;,\n            column: &quot;Name&quot;,\n            unique: true);\n    }\n\n    protected override void Down(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.DropTable(\n            name: &quot;Products&quot;);\n    }\n}\n</code></pre>\n<h2>Customizing Migrations</h2>\n<p>We can also modify the migration files if we need to apply some custom changes.</p>\n<p>A notable example is renaming a column.\nLet's say we rename the <code>Description</code> property to <code>ShortDescription</code>.\nIn some EF versions, this would result in the following migration:</p>\n<pre><code class=\"language-csharp\">migrationBuilder.DropColumn(\n    name: &quot;Description&quot;,\n    table: &quot;Customers&quot;);\n\nmigrationBuilder.AddColumn&lt;string&gt;(\n    name: &quot;ShortDescription&quot;,\n    table: &quot;Products&quot;,\n    nullable: true);\n</code></pre>\n<p>What's the problem here?\nBy calling <code>DropColumn</code> first, we will remove the column from the database and lose valuable data.</p>\n<p>What we actually want to do is rename the existing column.\nSo, we can modify the migration file to use the <code>RenameColumn</code> method:</p>\n<pre><code class=\"language-csharp\">migrationBuilder.RenameColumn(\n    name: &quot;Description&quot;,\n    table: &quot;Products&quot;,\n    newName: &quot;ShortDescription&quot;);\n</code></pre>\n<p>Another example is executing custom SQL commands from your migrations.\nCustom SQL commands are helpful when we can't express something through the EF fluent API.\nI've used it in the past to migrate data from one column to another or define a complex index.</p>\n<pre><code class=\"language-csharp\">public partial class Update_Products : Migration\n{\n    protected override void Up(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.Sql(&quot;&lt;YOUR CUSTOM SQL HERE&gt;&quot;);\n    }\n\n    protected override void Down(MigrationBuilder migrationBuilder)\n    {\n        // You are also responsible for reverthing any changes.\n    }\n}\n</code></pre>\n<h2>Migration SQL Scripts</h2>\n<p>You can use the <code>Script-Migration</code> command to generate SQL scripts from your migrations.\nThis is useful for reviewing changes before applying them to the database.\nSQL scripts allow us to execute migrations in environments without direct access to the EF tooling.</p>\n<p>Remember, you are responsible for preventing any data loss when executing EF migrations.\nReview the migrations carefully before applying them to the database.</p>\n<p>Here are a few ways you can execute the <code>Script-Migration</code> command:</p>\n<pre><code>Script-Migration\n\nScript-Migration &lt;FromMigration&gt;\n\nScript-Migration &lt;FromMigration&gt; &lt;ToMigration&gt;\n</code></pre>\n<p>The <code>&lt;FromMigration&gt;</code> argument should be the name of the last migration applied to the database.\nIt's your responsibility to apply the script appropriately, and only to databases in the correct migration state.</p>\n<p>Here's what the SQL script for the <code>Create_Database</code> migration looks like:</p>\n<pre><code class=\"language-sql\">CREATE TABLE IF NOT EXISTS &quot;__EFMigrationsHistory&quot; (\n    &quot;MigrationId&quot; character varying(150) NOT NULL,\n    &quot;ProductVersion&quot; character varying(32) NOT NULL,\n    CONSTRAINT &quot;PK___EFMigrationsHistory&quot; PRIMARY KEY (&quot;MigrationId&quot;)\n);\n\nSTART TRANSACTION;\n\nCREATE TABLE &quot;Products&quot; (\n    &quot;Id&quot; integer GENERATED BY DEFAULT AS IDENTITY,\n    &quot;Name&quot; character varying(100) NOT NULL,\n    &quot;Description&quot; character varying(1000),\n    &quot;Price&quot; numeric(18,2) NOT NULL,\n    CONSTRAINT &quot;PK_Products&quot; PRIMARY KEY (&quot;Id&quot;),\n    CONSTRAINT &quot;CK_Price_NotNegative&quot; CHECK (Price &gt; 0)\n);\n\nCREATE UNIQUE INDEX &quot;IX_Products_Name&quot; ON &quot;Products&quot; (&quot;Name&quot;);\n\nINSERT INTO &quot;__EFMigrationsHistory&quot; (&quot;MigrationId&quot;, &quot;ProductVersion&quot;)\nVALUES ('20240516095344_Create_Database', '8.0.5');\n\nCOMMIT;\n</code></pre>\n<p>You can also specify an <code>-Idempotent</code> argument to the <code>Script-Migration</code> command.\nThe <code>Script-Migration</code> command will generate SQL scripts that only apply migrations that haven't been applied already.\nThis is useful if you're not sure what the last migration applied to the database.</p>\n<pre><code>Script-Migration -Idempotent\n</code></pre>\n<p>Here's what the idempotent SQL script looks like:</p>\n<pre><code class=\"language-sql\">CREATE TABLE IF NOT EXISTS &quot;__EFMigrationsHistory&quot; (\n    &quot;MigrationId&quot; character varying(150) NOT NULL,\n    &quot;ProductVersion&quot; character varying(32) NOT NULL,\n    CONSTRAINT &quot;PK___EFMigrationsHistory&quot; PRIMARY KEY (&quot;MigrationId&quot;)\n);\n\nSTART TRANSACTION;\n\nDO $EF$\nBEGIN\n    IF NOT EXISTS(SELECT 1 FROM &quot;__EFMigrationsHistory&quot; WHERE &quot;MigrationId&quot; = '20240516095344_Create_Database') THEN\n    CREATE TABLE &quot;Products&quot; (\n        &quot;Id&quot; integer GENERATED BY DEFAULT AS IDENTITY,\n        &quot;Name&quot; character varying(100) NOT NULL,\n        &quot;Description&quot; character varying(1000),\n        &quot;Price&quot; numeric(18,2) NOT NULL,\n        CONSTRAINT &quot;PK_Products&quot; PRIMARY KEY (&quot;Id&quot;),\n        CONSTRAINT &quot;CK_Price_NotNegative&quot; CHECK (Price &gt; 0)\n    );\n    END IF;\nEND $EF$;\n\nDO $EF$\nBEGIN\n    IF NOT EXISTS(SELECT 1 FROM &quot;__EFMigrationsHistory&quot; WHERE &quot;MigrationId&quot; = '20240516095344_Create_Database') THEN\n    CREATE UNIQUE INDEX &quot;IX_Products_Name&quot; ON &quot;Products&quot; (&quot;Name&quot;);\n    END IF;\nEND $EF$;\n\nDO $EF$\nBEGIN\n    IF NOT EXISTS(SELECT 1 FROM &quot;__EFMigrationsHistory&quot; WHERE &quot;MigrationId&quot; = '20240516095344_Create_Database') THEN\n    INSERT INTO &quot;__EFMigrationsHistory&quot; (&quot;MigrationId&quot;, &quot;ProductVersion&quot;)\n    VALUES ('20240516095344_Create_Database', '8.0.5');\n    END IF;\nEND $EF$;\nCOMMIT;\n</code></pre>\n<h2>Applying Migrations</h2>\n<p>How do we apply EF migrations to the database?</p>\n<p>We have a few options:</p>\n<ul>\n<li>SQL scripts</li>\n<li>Command-line tools</li>\n<li>Apply migrations through code</li>\n<li>Migration bundles</li>\n</ul>\n<p>We discussed SQL scripts in the previous section, so I won't mention them again.</p>\n<h3>Command-line Tools</h3>\n<p>The most common approach to applying database migrations is using the CLI.\nYou can use either the <code>dotnet ef</code> tool or the PowerShell commands.\nFor example, you can execute the <code>Update-Database</code> command from PowerShell to apply any pending migrations.</p>\n<pre><code>Update-Database -Migration &lt;ToMigration&gt; -Connection &lt;ConnectionString&gt;\n</code></pre>\n<p>Here are the documentation links if you want to learn more:</p>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/ef/core/cli/\">EF Core CLI documentation</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/ef/core/cli/powershell\">EF Core PowerShell documentation</a></li>\n</ul>\n<h3>Applying Migrations through Code</h3>\n<p>Here's a helper method for applying database migrations.\nIt uses an <code>IServiceScope</code> to resolve a <code>DbContext</code> instance and uses it to call the <code>Migrate</code> method.</p>\n<pre><code class=\"language-csharp\">public static void ApplyMigration&lt;TDbContext&gt;(IServiceScope scope)\n    where TDbContext : DbContext\n{\n    using TDbContext context = scope.ServiceProvider\n        .GetRequiredService&lt;TDbContext&gt;();\n\n    context.Database.Migrate();\n}\n</code></pre>\n<p>You can apply migrations when the application is starting.\nI <strong>don't recommend</strong> using this approach for production environments.\nMigration can fail, concurrency issues exist, and rolling back migrations is challenging.\nHowever, this approach can be helpful in local development and when scaffolding databases for integration testing.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nvar app = builder.Build();\n\nif (app.Environment.IsDevelopment())\n{\n    using IServiceScope scope = app.ApplicationServices.CreateScope();\n\n    ApplyMigration&lt;AppDbContext&gt;(scope);\n}\n\napp.Run();\n</code></pre>\n<h3>Migration Bundles</h3>\n<p>Migration bundles are executable files that you can use to apply database migrations.\nThey're self-contained and can be executed from CI pipelines.</p>\n<p>You can use the <code>Bundle-Migration</code> command to create a migration bundle:</p>\n<pre><code>Bundle-Migration -Connection &lt;ConnectionString&gt;\n</code></pre>\n<p>This will create an <code>efbundle.exe</code> file that we can run to apply any pending database migrations.</p>\n<p>I recommend reading the <a href=\"https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying#bundles\">migration bundles documentation</a> to learn more.</p>\n<h2>Additional Database Migration Tools</h2>\n<p>What can you do if you don't want to use EF Core migrations?</p>\n<p>I wanted to mention some additional tools you can use for database schema versioning and running migrations:</p>\n<ul>\n<li><a href=\"https://github.com/fluentmigrator/fluentmigrator\">FluentMigrator</a>: A migration framework for .NET with a fluent API for defining migrations.</li>\n<li><a href=\"https://github.com/DbUp/DbUp\">DbUp</a>: A lightweight library for applying SQL scripts to databases.</li>\n<li><a href=\"https://erikbra.github.io/grate/\">Grate</a>: An automated database deployment (change management) system that relies on SQL scripts.</li>\n<li><a href=\"https://flywaydb.org/\">Flyway</a>: An open-source database migration tool that simplifies the management and versioning of database schema changes.</li>\n</ul>\n<p>We won't do a deep dive on these tools, but I recommend you check out their documentation.</p>\n<h2>EF Core Migrations Best Practices</h2>\n<p>I want to wrap up this issue with a few tips from my experience of working with EF Core migrations over the years:</p>\n<ul>\n<li><strong>Use meaningful migration names</strong>: Don't name migrations with dates or generic descriptions.\nUse clear, descriptive names that indicate the purpose of the migration.\nGood examples: <code>AddProductsTable</code>, <code>RenameDescriptionToShortDescription</code>.\nThis makes it much easier to understand your migration history and find specific changes.</li>\n<li><strong>Keep migrations small and focused</strong>: Avoid creating massive migrations containing multiple unrelated changes.\nSmaller migrations are easier to review, test, and troubleshoot if something goes wrong.\nAim for one migration per feature or logical change.</li>\n<li><strong>Test migrations thoroughly</strong>: Before applying migrations to production, test them in a development or staging environment.\nDevelopment and staging environments should mirror your production setup as closely as possible.\nThis will help catch any unexpected issues or data loss risks before they affect real users.</li>\n<li><strong>Beware of destructive changes</strong>: Some operations, like dropping columns or tables, can lead to irreversible data loss.\nCarefully consider the consequences before including such changes in migrations.\nProvide a way to migrate data or create a backup plan.</li>\n<li><strong>Avoid merge conflicts</strong>: Solving merge conflicts for EF migration snapshots can be a real headache.\nBe mindful of this when working in a team that creates many database migrations.\nIt's recommended to always be up-to-date with the latest migration before creating a new one.\nThis should minimize the chance of creating merge conflicts.</li>\n</ul>\n<p>My preferred approach to applying migrations is using SQL scripts.\nDepending on the project scope and complexity, we could do this manually or through a tool that automates the process.\nThis allows me to review the migration and identify any potential problems.</p>\n<p>I hope this was helpful!</p>\n<p>Thanks for reading, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide",
            "title": "EF Core Migrations: A Detailed Guide",
            "summary": "In this newsletter, we'll break down the essentials of EF Migrations. We'll explore creating migrations, SQL scripts, applying migrations, migration tooling…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_090.png",
            "date_modified": "2024-05-18T00:00:00.000Z",
            "date_published": "2024-05-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet",
            "content_html": "<p>.NET 8 introduced the <code>Microsoft.Extensions.Resilience</code> and <code>Microsoft.Extensions.Http.Resilience</code> packages, both built on top of Polly.\nYou compose retry, timeout, circuit breaker, fallback, hedging, and rate limiter strategies into a resilience pipeline, and the order you configure them matters.\nFor outgoing HTTP calls, <code>AddStandardResilienceHandler</code> applies a ready-made pipeline.</p>\n<p>From my experience working with microservices systems, things don't always go as planned.\nNetwork requests randomly fail, application servers become overloaded, and unexpected errors appear.\nThat's where resilience comes in.</p>\n<p>Resilient applications can recover from transient failures and continue to function.\nResilience is achieved by designing applications that can handle failures gracefully and recover quickly.</p>\n<p>By designing your applications with resilience in mind, you can create robust and reliable systems, even when the going gets tough.</p>\n<p>In this newsletter, we'll explore the tools and techniques we have in .NET to build resilient systems.</p>\n<h2>Resilience: Why You Should Care</h2>\n<p>Sending HTTP requests is a common approach for remote communication between services.\nHowever, HTTP requests are susceptible to failures from network or server issues.\nThese failures can disrupt service availability, especially as dependencies increase and the risk of cascading failures grows.</p>\n<p>So, how can you improve the resilience of your applications and services?</p>\n<p>Here are a few strategies you can consider to increase resilience:</p>\n<ul>\n<li><strong>Retries</strong>: Retry requests that fail due to transient errors.</li>\n<li><strong>Timeouts</strong>: Cancel requests that exceed a specified time limit.</li>\n<li><strong>Fallbacks</strong>: Define alternative actions or results for failed operations.</li>\n<li><strong>Circuit Breakers</strong>: Temporarily suspend communication with unavailable services.</li>\n</ul>\n<p>You can use these strategies individually or in combination for optimal HTTP request resilience.</p>\n<p>Let's see how we can introduce resilience in a .NET application.</p>\n<h2>Resilience Pipelines</h2>\n<p>With .NET 8, integrating resilience into your applications has become much simpler.\nWe can use <code>Microsoft.Extensions.Resilience</code> and <code>Microsoft.Extensions.Http.Resilience</code>, which are built on top of <a href=\"https://github.com/App-vNext/Polly\">Polly</a>.\nPolly is a .NET resilience and transient fault-handling library.\nPolly allows us to define resilience strategies such as retry, circuit breaker, timeout, rate-limiting, fallback, and hedging.</p>\n<p>Polly received a new API surface in its latest version (V8), which was implemented in collaboration with Microsoft.\nYou can learn more about the <a href=\"https://youtu.be/PqVQFUCTzUM\"><strong>Polly V8 API in this video</strong></a>.</p>\n<p>If you were previously using <code>Microsoft.Extensions.Http.Polly</code>, it is recommended that you switch to one of the previously mentioned packages.</p>\n<p>Let's start by installing the required NuGet packages:</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.Extensions.Resilience\nInstall-Package Microsoft.Extensions.Http.Resilience\n</code></pre>\n<p>To use resilience, you must first build a pipeline consisting of resilience <a href=\"https://www.pollydocs.org/strategies/\">strategies</a>.\nEach strategy that we configure as part of the pipeline will execute in order of configuration.\nOrder is important with resilience pipelines.\nKeep that in mind.</p>\n<p>We start by creating an instance of <code>ResiliencePipelineBuilder</code>, which allows us to configure resilience strategies.</p>\n<pre><code class=\"language-csharp\">ResiliencePipeline pipeline = new ResiliencePipelineBuilder()\n    .AddRetry(new RetryStrategyOptions\n    {\n        ShouldHandle = new PredicateBuilder().Handle&lt;ConflictException&gt;(),\n        Delay = TimeSpan.FromSeconds(1),\n        MaxRetryAttempts = 2,\n        BackoffType = DelayBackoffType.Exponential,\n        UseJitter = true\n    })\n    .AddTimeout(new TimeoutStrategyOptions\n    {\n        Timeout = TimeSpan.FromSeconds(10)\n    })\n    .Build();\n\nawait pipeline.ExecuteAsync(\n    async ct =&gt; await httpClient.GetAsync(&quot;https://modularmonolith.com&quot;, ct),\n    cancellationToken);\n</code></pre>\n<p>Here's what we're adding to the resilience pipeline:</p>\n<ul>\n<li><code>AddRetry</code> - Configures a retry resilience strategy, which we can further configure by passing in a <code>RetryStrategyOptions</code> instance.\nWe can provide a predicate for the <code>ShouldHandle</code> property to define which exceptions the resilience strategy should handle.\nThe retry strategy also comes with some sensible <a href=\"https://www.pollydocs.org/strategies/retry.html#defaults\">default values</a>.</li>\n<li><code>AddTimeout</code> - Configures a timeout strategy that will throw a <code>TimeoutRejectedException</code> if the delegate does not complete before the timeout.\nWe can provide a custom timeout by passing in a <code>TimeoutStrategyOptions</code> instance.\nThe default timeout is 30 seconds.</li>\n</ul>\n<p>Finally, we can <code>Build</code> the resilience pipeline and get back a configured <code>ResiliencePipeline</code> instance that will apply the respective resilience strategies.\nTo use the <code>ResiliencePipeline</code>, we can call the <code>ExecuteAsync</code> method and pass in a delegate.</p>\n<h2>Resilience Pipelines and Dependency Injection</h2>\n<p>Configuring a resilience pipeline every time we want to use it is cumbersome.\n.NET 8 introduces a new extension method for the <code>IServiceCollection</code> interface that allows us to register resilience pipelines with dependency injection.</p>\n<p>Instead of manually configuring resilience every time, you ask for a pre-made pipeline by name.</p>\n<p>We start by calling the <code>AddResiliencePipeline</code> method, which allows us to configure the resilience pipeline.\nEach resilience pipeline needs to have a unique key.\nWe can use this key to resolve the respective resilience pipeline instance.</p>\n<p>In this example, we're passing in a <code>string</code> key which allows us to configure the non-generic <code>ResiliencePipelineBuilder</code>.</p>\n<pre><code class=\"language-csharp\">services.AddResiliencePipeline(&quot;retry&quot;, builder =&gt;\n{\n    builder.AddRetry(new RetryStrategyOptions\n    {\n        Delay = TimeSpan.FromSeconds(1),\n        MaxRetryAttempts = 2,\n        BackoffType = DelayBackoffType.Exponential,\n        UseJitter = true\n    });\n});\n</code></pre>\n<p>However, we can also specify generic arguments when calling <code>AddResiliencePipeline</code>.\nThis allows us to configure a typed resilience pipeline using <code>ResiliencePipelineBuilder&lt;TResult&gt;</code>.\nUsing this approach, we can access the <a href=\"https://www.pollydocs.org/strategies/hedging.html\">hedging</a> and <a href=\"https://www.pollydocs.org/strategies/fallback.html\">fallback</a> strategies.</p>\n<p>In the following example, we're configuring a fallback strategy by calling <code>AddFallback</code>.\nThis allows us to provide a fallback value that we can return in case of a failure.\nThe fallback could be a static value or come from another HTTP request or the database.</p>\n<pre><code class=\"language-csharp\">services.AddResiliencePipeline&lt;string, GitHubUser?&gt;(&quot;gh-fallback&quot;, builder =&gt;\n{\n    builder.AddFallback(new FallbackStrategyOptions&lt;GitHubUser?&gt;\n    {\n        FallbackAction = _ =&gt;\n            Outcome.FromResultAsValueTask&lt;GitHubUser?&gt;(GitHubUser.Empty)\n    });\n});\n</code></pre>\n<p>To use resilience pipelines configured with dependency injection, we can use the <code>ResiliencePipelineProvider</code>.\nIt exposes a <code>GetPipeline</code> method for obtaining the pipeline instance.\nWe have to provide the key used to register the resilience pipeline.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;users&quot;, async (\n    HttpClient httpClient,\n    ResiliencePipelineProvider&lt;string&gt; pipelineProvider) =&gt;\n{\n    ResiliencePipeline&lt;GitHubUser?&gt; pipeline =\n        pipelineProvider.GetPipeline&lt;GitHubUser?&gt;(&quot;gh-fallback&quot;);\n\n    var user = await pipeline.ExecuteAsync(async token =&gt;\n        await httpClient.GetAsync(&quot;api/users&quot;, token),\n        cancellationToken);\n});\n</code></pre>\n<h2>Resilience Strategies and Polly</h2>\n<p><a href=\"https://www.pollydocs.org/strategies/\">Resilience strategies</a> are the core component of Polly.\nThey're designed to run custom callbacks while introducing an additional layer of resilience.\nWe can't run these strategies directly.\nInstead, we execute them through a resilience pipeline.</p>\n<p>Polly categorizes resilience strategies into <strong>reactive</strong> and <strong>proactive</strong>.\nReactive strategies handle specific exceptions or results.\nProactive strategies decide to cancel or reject the execution of callbacks using a rate limiter or a timeout resilience strategy.</p>\n<p>Polly has the following built-in resilience strategies:</p>\n<ul>\n<li><strong>Retry</strong>: The classic &quot;try again&quot; approach.\nWorks great for temporary network glitches.\nYou can configure how many retries you have and even add some randomness (jitter) to avoid overloading the system if everyone retries at once.</li>\n<li><strong>Circuit-breaker</strong>: Like an electrical circuit breaker, this prevents hammering a failing system.\nIf errors pile up, the circuit breaker &quot;trips&quot; temporarily to give the system time to recover.</li>\n<li><strong>Fallback</strong>: Provides a safe, default response if your primary call fails.\nIt might be a cached result or a simple &quot;service unavailable&quot; message.</li>\n<li><strong>Hedging</strong>: Makes multiple requests simultaneously, taking the first successful response.\nIt is helpful if your system has numerous ways of handling something.</li>\n<li><strong>Timeout</strong>: Prevents requests from hanging forever by terminating them if the timeout is exceeded.</li>\n<li><strong>Rate-limiter</strong>: Throttles outgoing requests to prevent overwhelming external services.</li>\n</ul>\n<h2>HTTP Request Resilience</h2>\n<p>Sending HTTP calls to external services is how your application interacts with the outside world.\nThese could be third-party services like payment gateways and identity providers or other services your team owns and operates.</p>\n<p>The <code>Microsoft.Extensions.Http.Resilience</code> library comes with ready-to-use resilience pipelines for sending HTTP requests.</p>\n<p>We can add resilience to outgoing <a href=\"https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet\"><strong>HttpClient requests</strong></a> using the <code>AddStandardResilienceHandler</code> method.</p>\n<pre><code class=\"language-csharp\">services.AddHttpClient&lt;GitHubService&gt;(static (httpClient) =&gt;\n{\n    httpClient.BaseAddress = new Uri(&quot;https://api.github.com/&quot;);\n})\n.AddStandardResilienceHandler();\n</code></pre>\n<p>This also means you can eliminate any <a href=\"https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore\"><strong>delegating handlers</strong></a> you previously used for resilience.</p>\n<p>The standard resilience handler combines five Polly strategies to create a resilience pipeline suitable for most scenarios.\nThe standard pipeline contains the following strategies:</p>\n<ul>\n<li><strong>Rate limiter</strong>: Limits the maximum number of concurrent requests sent to the dependency.</li>\n<li><strong>Total request timeout</strong>: Introduces a total timeout, including any retry attempts.</li>\n<li><strong>Retry</strong>: Retries a request if it fails because of a timeout or a transient error.</li>\n<li><strong>Circuit breaker</strong>: Prevents sending further requests if too many failures are detected.</li>\n<li><strong>Attempt timeout</strong>: Introduces a timeout for an individual request.</li>\n</ul>\n<p>You can customize any aspect of the standard resilience pipeline by configuring the <code>HttpStandardResilienceOptions</code>.</p>\n<h2>Takeaway</h2>\n<p>Resilience isn't just a buzzword; it's a core principle for building reliable software systems.\nWe're fortunate to have powerful tools like <code>Microsoft.Extensions.Resilience</code> and <a href=\"https://milanjovanovic.tech/blog/polly-v8-resilience-pipelines\"><strong>Polly</strong></a> at our disposal.\nWe can use them to design systems that gracefully handle any transient failures.</p>\n<p>Good <a href=\"https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet\"><strong>monitoring and observability</strong></a>\nare essential to understand how your resilience mechanisms work in production.\nRemember, the goal isn't to eliminate failures but to gracefully handle them and keep your application functioning.</p>\n<p>Ready to dive deeper into resilient architecture?\nMy advanced course on <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>building modular monoliths</strong></a> will equip you with the skills to design and implement robust, scalable systems.\nCheck out <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith Architecture</strong></a>.</p>\n<p><strong>Challenge</strong>: Take a look at your existing .NET projects.\nAre there any critical areas where a little resilience could go a long way?\nPick one and try applying some of the techniques we've discussed here.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet",
            "title": "Building Resilient Cloud Applications With .NET",
            "summary": "By designing your applications with resilience in mind, you can create robust and reliable systems, even when the going gets tough.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_089.png",
            "date_modified": "2024-05-11T00:00:00.000Z",
            "date_published": "2024-05-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-api-gateway-authentication-with-yarp",
            "content_html": "<p>YARP does not authenticate or authorize requests until you enable it.\nAdd the ASP.NET Core authentication and authorization middleware before <code>MapReverseProxy</code>, then set <code>AuthorizationPolicy</code> on each route.\nThe <code>default</code> value requires an authenticated user, <code>anonymous</code> skips authorization, and a named policy gives you custom claim-based rules.</p>\n<p>API gateways provide clients with a single point of entry.\nThis streamlines their interactions with your system and ensures the security of your <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>microservices</strong></a> or distributed system.</p>\n<p>One critical aspect of API gateways is authentication - ensuring only authorized users and applications can access your valuable data and resources.</p>\n<p>In this newsletter, we'll explore how you can implement API gateway authentication using <a href=\"https://microsoft.github.io/reverse-proxy/index.html\">YARP</a> (Yet Another Reverse Proxy),\na powerful and flexible reverse proxy library for .NET applications.</p>\n<p>Here's what we will cover:</p>\n<ul>\n<li>The role of API gateways</li>\n<li>Configuring authentication with YARP</li>\n<li>Creating custom authorization policies</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Role of API Gateways</h2>\n<p>An <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>API gateway</strong></a> is the &quot;front door&quot; to your backend services and APIs.\nIt acts as an intermediary layer, handling client requests and routing them to the appropriate destinations.</p>\n<p>The key benefits of API gateways are:</p>\n<ul>\n<li><strong>Centralized access</strong>: All incoming requests must first pass through the gateway. This simplifies management and monitoring.</li>\n<li><strong>Service abstraction</strong>: Clients interact only with the gateway. We can hide the complexity of the backend architecture from clients.</li>\n<li><strong>Performance enhancement</strong>: Implement techniques like caching and <a href=\"https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing\"><strong>load balancing</strong></a> to optimize API performance.</li>\n<li><strong>Authentication and Authorization</strong>: API gateways verify user and application identities, enforcing whether a request is allowed or not.</li>\n</ul>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_088/api_gateway.png\" alt=\"API Gateway diagram.\">\n  <figcaption>\n    Source: <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\">Modular Monolith Architecture</a>\n  </figcaption>\n</figure>\n<h2>Configuring Authentication With YARP</h2>\n<p>We can use the API gateway to authenticate and authorize requests before they are proxied to the destination servers.\nThis can reduce the load on the destination servers, and introduce a layer of security.\nImplementing authentication on the API gateway ensures consistent policies are implemented across your applications.</p>\n<p>If you're new to YARP, I recommend first reading about <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>how to implement an API gateway with YARP</strong></a>.</p>\n<p>By default, YARP won't authenticate or authorize requests unless enabled in the route or application configuration.</p>\n<p>We can start by introducing <a href=\"https://docs.microsoft.com/aspnet/core/security/authentication/\">authentication</a>\nand <a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction\">authorization</a> middleware:</p>\n<pre><code class=\"language-csharp\">app.UseAuthentication();\napp.UseAuthorization();\n\napp.MapReverseProxy();\n</code></pre>\n<p>This allows us to configure the authorization policy by providing the <code>AuthorizationPolicy</code> value in the route configuration.</p>\n<p>There are two special values we can specify in a route's authorization parameter:</p>\n<ul>\n<li><code>default</code> - The route will require an authenticated user.</li>\n<li><code>anonymous</code> - The route will not require authorization regardless of any other configuration.</li>\n</ul>\n<p>Here's how we can enforce that all incoming requests must be authenticated:</p>\n<pre><code class=\"language-json\">{\n  // This is how we define reverse proxy routes.\n  &quot;Routes&quot;: {\n    &quot;api-route&quot;: {\n      &quot;ClusterId&quot;: &quot;api-cluster&quot;,\n      &quot;AuthorizationPolicy&quot;: &quot;default&quot;,\n      &quot;Match&quot;: {\n        &quot;Path&quot;: &quot;api/{**catch-all}&quot;\n      }\n    }\n  }\n}\n</code></pre>\n<p>We want to authorize any incoming request as soon as it hits the API gateway.\nHowever, the destination server may still need to know who the user is (authentication) and what they can do (authorization).</p>\n<p>YARP will pass any credentials to the proxied request.\nBy default, cookies, <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>bearer tokens</strong></a>, and API keys will flow to the destination server.</p>\n<h2>Creating Custom Authentication Policies</h2>\n<p>YARP can utilize the powerful <a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies\">authorization policies</a> feature in ASP.NET Core.\nWe can specify a policy per route in the proxy configuration, and the rest is handled by existing ASP.NET Core authentication and authorization components.</p>\n<pre><code class=\"language-json\">{\n  // This is how we define auth policies for reverse proxy routes.\n  &quot;Routes&quot;: {\n    &quot;api-route1&quot;: {\n      &quot;ClusterId&quot;: &quot;api-cluster&quot;,\n      &quot;AuthorizationPolicy&quot;: &quot;is-vip&quot;,\n      &quot;Match&quot;: {\n        &quot;Path&quot;: &quot;api/hello-vip&quot;\n      }\n    },\n    &quot;api-route2&quot;: {\n      &quot;ClusterId&quot;: &quot;api-cluster&quot;,\n      &quot;AuthorizationPolicy&quot;: &quot;default&quot;,\n      &quot;Match&quot;: {\n        &quot;Path&quot;: &quot;api/{**catch-all}&quot;\n      }\n    }\n  }\n}\n</code></pre>\n<p>Here's how we can create a custom <code>is-vip</code> policy with two components.\nIt requires an authenticated user and <code>vip</code> claim with one of the defined allowed values to be present .\nTo use this policy, we can just specify it as the value for the <code>AuthorizationPolicy</code> in the route configuration.</p>\n<pre><code class=\"language-csharp\">services.AddAuthorization(options =&gt;\n{\n    options.AddPolicy(&quot;is-vip&quot;, policy =&gt;\n        policy\n            .RequireAuthenticatedUser()\n            .RequireClaim(&quot;vip&quot;, allowedValues: true.ToString()));\n});\n</code></pre>\n<h2>Summary</h2>\n<p>API gateways provide a unified access point, streamlining client interactions and securing your backend services.\nAuthentication is an essential element of API gateway security, controlling who can access your resources.</p>\n<p>YARP offers a versatile solution for building .NET API gateways.\nBy integrating with ASP.NET Core's authentication and authorization frameworks, YARP enables robust security mechanisms.</p>\n<p>This flexibility really shines with support for custom authorization policies.\nThis allows you to define <a href=\"https://milanjovanovic.tech/blog/master-claims-transformation-for-flexible-aspnetcore-authorization\"><strong>granular access control</strong></a>\nbased on user roles, claims, or other attributes.</p>\n<p>Thanks for reading, and I'll see you next week.</p>\n<p><strong>P.S.</strong> Here's the complete <a href=\"https://github.com/m-jovanovic/yarp-authentication\">source code</a> for this article if you want to try it out.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-api-gateway-authentication-with-yarp",
            "title": "Implementing API Gateway Authentication With YARP",
            "summary": "In this newsletter, we'll explore how you can implement API gateway authentication using YARP (Yet Another Reverse Proxy), a powerful and flexible reverse…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_088.png",
            "date_modified": "2024-05-04T00:00:00.000Z",
            "date_published": "2024-05-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/request-response-messaging-pattern-with-masstransit",
            "content_html": "<p>The request-response messaging pattern works like a function call, except the request and the response travel over a message bus.\nMassTransit supports it out of the box through a request client: you inject an <code>IRequestClient&lt;T&gt;</code>, send the request, and await the response.\nThe services stay loosely coupled because they only share message contracts.</p>\n<p>Building distributed applications might seem simple at first. It's just servers talking to each other. Right?</p>\n<p>However, it opens a set of potential problems you must consider.\nWhat if the network has a hiccup?\nA service unexpectedly crashes?\nYou try to scale, and everything crumbles under the load?\nThis is where the way your distributed system communicates becomes critical.</p>\n<p>Traditional synchronous communication, where services call each other directly, is inherently fragile.\nIt creates tight coupling, making your whole application vulnerable to single points of failure.</p>\n<p>To combat this, we can turn to distributed messaging\n(and introduce an entirely different set of problems, but that's a story for another issue).</p>\n<p>One powerful tool for achieving this in the .NET world is MassTransit.</p>\n<p>In this week's issue, we'll explore MassTransit's implementation of the request-response pattern.</p>\n<h2>Request-Response Messaging Pattern Introduction</h2>\n<p>Let's start by explaining how the request-response messaging pattern works.</p>\n<p>The request-response pattern is just like making a traditional function call but over the network.\nOne service, the requester, sends a request message and waits for a corresponding response message.\nThis is a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>synchronous communication approach</strong></a> from the requester's side.</p>\n<p>The good parts:</p>\n<ul>\n<li><strong>Loose Coupling</strong>: Services don't need direct knowledge of each other, only of the message contracts.\nThis makes changes and scaling easier.</li>\n<li><strong>Location Transparency</strong>: The requester doesn't need to know <em>where</em> the responder is located, leading to improved flexibility.</li>\n</ul>\n<p>The bad parts:</p>\n<ul>\n<li><strong>Latency</strong>: The overhead of messaging adds some additional latency.</li>\n<li><strong>Complexity</strong>: Introducing a messaging system and managing the additional infrastructure can increase project complexity.</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_087/request_response.png\" alt=\"Request response messaging pattern diagram.\">\n<h2>Request-Response Messaging With MassTransit</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus\"><strong>MassTransit</strong></a>\nsupports the <a href=\"https://masstransit.io/documentation/concepts/requests\">request-response</a> messaging pattern out of the box.\nWe can use a <strong>request client</strong> to send requests and wait for a response.\nThe request client is asynchronous and supports the <code>await</code> keyword.\nThe request will also have a timeout of 30 seconds by default, to prevent waiting for the response for too long.</p>\n<p>Let's imagine a scenario where you have an order processing system that needs to fetch an order's latest status.\nWe can fetch the status from an Order Management service.\nWith MassTransit, you'll create a request client to initiate the process.\nThis client will send a <code>GetOrderStatusRequest</code> message onto the bus.</p>\n<pre><code class=\"language-csharp\">public record GetOrderStatusRequest\n{\n    public string OrderId { get; init; }\n}\n</code></pre>\n<p>On the Order Management side, a responder (or consumer) will be listening for <code>GetOrderStatusRequest</code> messages.\nIt receives the request, potentially queries a database to get the status,\nand then sends a <code>GetOrderStatusResponse</code> message back onto the bus.\nThe original request client will be waiting for this response and can then process it accordingly.</p>\n<pre><code class=\"language-csharp\">public class GetOrderStatusRequestConsumer : IConsumer&lt;GetOrderStatusRequest&gt;\n{\n    public async Task Consume(ConsumeContext&lt;GetOrderStatusRequest&gt; context)\n    {\n        // Get the order status from a database.\n\n        await context.ResponseAsync&lt;GetOrderStatusResponse&gt;(new\n        {\n            // Set the respective response properties.\n        });\n    }\n}\n</code></pre>\n<h2>Getting User Permissions In a Modular Monolith</h2>\n<p>Here's a real-world scenario where my team decided to implement this pattern.\nWe were building a <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>modular monolith</strong></a>,\nand one of the modules was responsible for managing user permissions.\nThe other modules could call out to the Users module to get the user's permissions.\nAnd this works great while we are still inside a monolith system.</p>\n<p>However, at one point we needed to <a href=\"https://milanjovanovic.tech/blog/when-to-extract-module-to-microservice\"><strong>extract one module into a separate service</strong></a>.\nThis meant that the communication with the Users module using simple method calls would no longer work.</p>\n<p>Luckily, we were already using MassTransit and <a href=\"https://milanjovanovic.tech/blog/rabbitmq-vs-kafka-dotnet\"><strong>RabbitMQ</strong></a> for messaging inside the system.</p>\n<p>So, we decided to use the MassTransit request-response feature to implement this.</p>\n<p>The new service will inject an <code>IRequestClient&lt;GetUserPermissions&gt;</code>.\nWe can use it to send a <code>GetUserPermissions</code> message and await a response.</p>\n<p>A very powerful feature of MassTransit is that you can await more than one response message.\nIn this example, we're waiting for a <code>PermissionsResponse</code> or an <code>Error</code> response.\nThis is great, because we also have a way to handle failures in the consumer.</p>\n<pre><code class=\"language-csharp\">internal sealed class PermissionService(\n    IRequestClient&lt;GetUserPermissions&gt; client)\n    : IPermissionService\n{\n    public async Task&lt;Result&lt;PermissionsResponse&gt;&gt; GetUserPermissionsAsync(\n        string identityId)\n    {\n        var request = new GetUserPermissions(identityId);\n\n        Response&lt;PermissionsResponse, Error&gt; response =\n            await client.GetResponse&lt;PermissionsResponse, Error&gt;(request);\n\n        if (response.Is(out Response&lt;Error&gt; errorResponse))\n        {\n            return Result.Failure&lt;PermissionsResponse&gt;(errorResponse.Message);\n        }\n\n        if (response.Is(out Response&lt;PermissionsResponse&gt; permissionResponse))\n        {\n            return permissionResponse.Message;\n        }\n\n        return Result.Failure&lt;PermissionsResponse&gt;(NotFound);\n    }\n}\n</code></pre>\n<p>In the Users module, we can easily implement the <code>GetUserPermissionsConsumer</code>.\nIt will respond with a <code>PermissionsResponse</code> if the permissions are found or an <code>Error</code> in case of a failure.</p>\n<pre><code class=\"language-csharp\">public sealed class GetUserPermissionsConsumer(\n    IPermissionService permissionService)\n    : IConsumer&lt;GetUserPermissions&gt;\n{\n    public async Task Consume(ConsumeContext&lt;GetUserPermissions&gt; context)\n    {\n        Result&lt;PermissionsResponse&gt; result =\n            await permissionService.GetUserPermissionsAsync(\n                context.Message.IdentityId);\n\n        if (result.IsSuccess)\n        {\n            await context.RespondAsync(result.Value);\n        }\n        else\n        {\n            await context.RespondAsync(result.Error);\n        }\n    }\n}\n</code></pre>\n<h2>Closing Thoughts</h2>\n<p>By embracing messaging patterns with MassTransit, you're building on a much sturdier foundation.\nYour .NET services are now less tightly coupled, giving you the flexibility to evolve them independently\nand weather those inevitable network glitches or service outages.</p>\n<p>The <a href=\"https://youtu.be/NjsoykEOkrk\">request-response pattern</a> is a powerful tool in your messaging arsenal.\nMassTransit makes it remarkably easy to implement, ensuring that requests and responses are delivered reliably.</p>\n<p>We can use request-response to implement communication between modules in a <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>modular monolith</strong></a>.\nHowever, don't take this to the extreme, or your system might suffer from increased latency.</p>\n<p>Start small, experiment, and see how the reliability and flexibility of messaging can transform your development experience.</p>\n<p>That's all for this week. Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/request-response-messaging-pattern-with-masstransit",
            "title": "Request-Response Messaging Pattern With MassTransit",
            "summary": "When building distributed systems with .NET, direct calls between services can create tight coupling.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_087.png",
            "date_modified": "2024-04-27T00:00:00.000Z",
            "date_published": "2024-04-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet",
            "content_html": "<p>Distributed tracing follows a single request as it flows across services, databases, and message buses.\nIn .NET, you collect those traces with OpenTelemetry, an open-source standard that also covers metrics and logs.\nYou install the instrumentation packages, configure <code>AddOpenTelemetry</code>, and export the data to a backend like Jaeger.</p>\n<p>If you're building or maintaining distributed .NET applications, understanding how they behave is key to ensuring reliability and performance.</p>\n<p>Distributed systems offer flexibility but introduce complexity, making troubleshooting a headache.\nUnderstanding how requests flow through your system is crucial for debugging and performance optimization.</p>\n<p>OpenTelemetry is an open-source observability framework that makes this possible.</p>\n<p>In this article, we'll dive into what OpenTelemetry is, how to use it in your .NET projects, and the powerful insights it provides.</p>\n<h2>OpenTelemetry Introduction</h2>\n<p><a href=\"https://opentelemetry.io/\">OpenTelemetry</a> (OTel) is a vendor-neutral, open-source standard for instrumenting applications to generate telemetry data.\nOpenTelemetry contains APIs, SDKs, tools, and integrations for creating and managing this telemetry data (<a href=\"https://milanjovanovic.tech/blog/opentelemetry-dotnet-guide\"><strong>traces, metrics, and logs</strong></a>).</p>\n<p>Telemetry data includes:</p>\n<ul>\n<li><strong>Traces</strong>: Represent the flow of requests through distributed systems, showing timings and relationships between services.</li>\n<li><strong>Metrics</strong>: Numerical measurements of system behavior over time (e.g., request counts, error rates, memory usage).</li>\n<li><strong>Logs</strong>: Textual records of events with rich contextual information. Structured logs.</li>\n</ul>\n<figure>\n  <img src=\"https://milanjovanovic.tech/blogs/mnw_086/otel.png\" alt=\"OpenTelemetry Reference Architecture.\">\n  <figcaption>\n    Source: <a href=\"https://opentelemetry.io/docs/\"><a href=\"https://opentelemetry.io/docs/\">https://opentelemetry.io/docs/</a></a>\n  </figcaption>\n</figure>\n<p>OpenTelemetry provides a unified way to collect this data, making it easier to understand the behavior and health of complex distributed applications.</p>\n<p>We can export the telemetry data we are collecting to a service capable of processing it and providing us with an interface to analyze it.</p>\n<p>We're going to configure OpenTelemetry to export traces directly to <a href=\"https://www.jaegertracing.io/\">Jaeger</a>.</p>\n<h2>Adding OpenTelemetry to .NET Applications</h2>\n<p>OpenTelemetry provides libraries and SDKs to add code (instrumentation) into your .NET applications.\nThese instrumentations automatically capture the traces, metrics, and logs we are interested in.</p>\n<p>We're going to install the following NuGet packages:</p>\n<pre><code class=\"language-powershell\"># Automatic tracing, metrics\nInstall-Package OpenTelemetry.Extensions.Hosting\n\n# Telemetry data exporter\nInstall-Package OpenTelemetry.Exporter.OpenTelemetryProtocol\n\n# Instrumentation packages\nInstall-Package OpenTelemetry.Instrumentation.Http\nInstall-Package OpenTelemetry.Instrumentation.AspNetCore\nInstall-Package OpenTelemetry.Instrumentation.EntityFrameworkCore\nInstall-Package OpenTelemetry.Instrumentation.StackExchangeRedis\nInstall-Package Npgsql.OpenTelemetry\n</code></pre>\n<p>Once we have these NuGet packages installed, it's time to configure some services.</p>\n<pre><code class=\"language-csharp\">services\n    .AddOpenTelemetry()\n    .ConfigureResource(resource =&gt; resource.AddService(serviceName))\n    .WithTracing(tracing =&gt;\n    {\n        tracing\n            .AddAspNetCoreInstrumentation()\n            .AddHttpClientInstrumentation()\n            .AddEntityFrameworkCoreInstrumentation()\n            .AddRedisInstrumentation()\n            .AddNpgsql();\n\n        tracing.AddOtlpExporter();\n    });\n</code></pre>\n<ul>\n<li><code>AddAspNetCoreInstrumentation</code> - This enables ASP.NET Core instrumentation.</li>\n<li><code>AddHttpClientInstrumentation</code> - This enables <code>HttpClient</code> instrumentation for outgoing requests.</li>\n<li><code>AddEntityFrameworkCoreInstrumentation</code> - This enables EF Core instrumentation.</li>\n<li><code>AddRedisInstrumentation</code> - This enables Redis instrumentation.</li>\n<li><code>AddNpgsql</code> - This enables PostgreSQL instrumentation.</li>\n</ul>\n<p>With all of these instrumentations configured, our application will start collecting a lot of valuable traces at runtime.</p>\n<p>We also need to configure an environment variable for the exporter added with <code>AddOtlpExporter</code> to work correctly.\nWe can set <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> through application settings.\nThe address specified here will point to a local Jaeger instance.</p>\n<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317\n</code></pre>\n<h2>Running Jaeger Locally</h2>\n<p><a href=\"https://www.jaegertracing.io/\">Jaeger</a> is an open source, distributed tracing platform.\nJaeger maps the flow of requests and data as they travel through a distributed system.\nThese requests could be calling out to multiple services, and Jaeger knows how to piece all of this information together.</p>\n<p>Here's how to run Jaeger inside a <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker container</strong></a>:</p>\n<pre><code>docker run -d -p 4317:4317 -p 16686:16686 jaegertracing/all-in-one:latest\n</code></pre>\n<p>We're using the <code>jaegertracing/all-in-one:latest</code> image, and exposing the <code>4317</code> to accept telemetry data.\nThe Jaeger user interface will be exposed on the <code>16686</code> port.</p>\n<h2>Distributed Tracing</h2>\n<p>After installing the OpenTelemetry libraries and configuring tracing in our applications, we can send some requests to generate telemetry data.\nWe can then access Jaeger to start analyzing our distributed traces.</p>\n<p><strong>Registering a new user</strong></p>\n<p>Here's an example of registering a new user with the system.\nWe're accessing the API gateway (<code>Evently.Gateway</code>) service, which proxies the request to the <code>Evently.Api</code> service.\nAnd you can see that the <code>Evently.Api</code> service makes a few HTTP requests before persisting a new record in the database.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_086/trace_1.png\" alt=\"Distributed trace.\">\n<p><strong>Publishing a message with MassTransit</strong></p>\n<p>Here's another distributed trace where we publish the <code>UserRegisteredIntegrationEvent</code> over a message bus.\nYou can see that it's being consumed by two different services that write some data to the database.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_086/trace_2.png\" alt=\"Distributed trace.\">\n<p><strong>Examining additional trace information</strong></p>\n<p>Distributed traces can include some useful contextual information.\nHere's an example trace representing a database command.\nThis comes from the PostgreSQL instrumentation, and we can see the SQL query that we are executing.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_086/trace_3.png\" alt=\"Distributed trace.\">\n<p><strong>Complex distributed traces</strong></p>\n<p>Here's a more complex distributed trace, which includes:</p>\n<ul>\n<li>Three .NET applications</li>\n<li>PostgreSQL database</li>\n<li>Redis distributed cache</li>\n</ul>\n<p>We're sending a request to get the customer's cart.\nThe request will first hit the API gateway, which proxies it to the <code>Evently.Ticketing.Api</code> service that owns the data.\nHowever, the <code>Evently.Ticketing.Api</code> service needs to reach out to the <code>Evently.Api</code> service to get the authorization information.\nAnd all of this leads to the distributed trace you can see below.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_086/trace_4.png\" alt=\"Distributed trace.\">\n<h2>Summary</h2>\n<p>Understanding modern applications, especially distributed ones, can be a real mind-bender.\nOpenTelemetry is like having X-ray vision into your system.</p>\n<p>While adding OpenTelemetry takes some upfront work, consider it an investment.\nThat investment pays off big time when problems pop up.\nInstead of frantic guesswork, you have precise data to zero in on issues fast.</p>\n<p>Is OpenTelemetry a magic bullet for all your problems? Nope.</p>\n<p>But it's an excellent tool to add to your troubleshooting arsenal, especially as your .NET applications grow and get more complex.</p>\n<p>If you're curious where the distributed traces come from, it's from the application we're building in my <a href=\"https://milanjovanovic.tech/modular-monolith-architecture\"><strong>Modular Monolith course</strong></a>.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/introduction-to-distributed-tracing-with-opentelemetry-in-dotnet",
            "title": "Introduction to Distributed Tracing With OpenTelemetry in .NET",
            "summary": "Distributed systems offer flexibility but introduce complexity, making troubleshooting a headache.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_086.png",
            "date_modified": "2024-04-20T00:00:00.000Z",
            "date_published": "2024-04-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core",
            "content_html": "<p>EF Core has no built-in mechanism for pessimistic locking, so you implement it with a raw SQL query.\nIn PostgreSQL, <code>SELECT FOR UPDATE</code> acquires a row-level lock and blocks competing transactions until the current transaction releases the lock.\nOn SQL Server, the <code>WITH (UPDLOCK, READPAST)</code> query hint gives you a similar effect.</p>\n<p>Sometimes, especially in high-traffic scenarios, you absolutely need to ensure that only one process can modify a piece of data at a time.</p>\n<p>Imagine you're building the ticket sales system for a wildly popular concert.\nCustomers are eagerly grabbing tickets, and the last few could sell out simultaneously.\nIf you're not careful, multiple customers might think they've secured the final seat, leading to overbooking and disappointment!</p>\n<p>Entity Framework Core is a fantastic tool, but it doesn't have a direct mechanism for pessimistic locking.\n<a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\">Optimistic locking</a> (using versions) can work, but in high-contention scenarios, it can lead to a lot of retries.</p>\n<p>So, how can we solve this problem with EF Core?</p>\n<h2>The Scenario in More Detail</h2>\n<p>Here's a simplified code snippet to illustrate our ticketing challenge:</p>\n<pre><code class=\"language-csharp\">public async Task Handle(CreateOrderCommand request)\n{\n    await using DbTransaction transaction = await unitOfWork\n        .BeginTransactionAsync();\n\n    Customer customer = await customerRepository.GetAsync(request.CustomerId);\n\n    Order order = Order.Create(customer);\n    Cart cart = await cartService.GetAsync(customer.Id);\n\n    foreach (CartItem cartItem in cart.Items)\n    {\n        // Uh-oh... what if two requests hit this at the same time?\n        TicketType ticketType = await ticketTypeRepository.GetAsync(\n            cartItem.TicketTypeId);\n\n        ticketType.UpdateQuantity(cartItem.Quantity);\n\n        order.AddItem(ticketType, cartItem.Quantity, cartItem.Price);\n    }\n\n    orderRepository.Insert(order);\n\n    await unitOfWork.SaveChangesAsync();\n\n    await transaction.CommitAsync();\n\n    await cartService.ClearAsync(customer.Id);\n}\n</code></pre>\n<p>The example above is contrived, but it should be enough to explain the problem.\nDuring checkout, we verify the <code>AvailableQuantity</code> for each ticket.</p>\n<p>What will happen if we get concurrent requests trying to purchase the same ticket?</p>\n<p>The worst-case scenario is we end up &quot;overselling&quot; the tickets.\nConcurrent requests could see available tickets for sale and complete the checkout.</p>\n<p>So, how do we solve this?</p>\n<h2>Raw SQL to the Rescue!</h2>\n<p>Since EF Core doesn't offer pessimistic locking directly, we'll dip into a bit of good old-fashioned SQL.\nWe will replace the <code>GetAsync</code> call to fetch the ticket with <code>GetWithLockAsync</code>.</p>\n<p>Thankfully, EF Core makes this easy with <a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries\">raw SQL queries</a>:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;TicketType&gt; GetWithLockAsync(Guid id)\n{\n    return await context\n        .TicketTypes\n        .FromSql(\n            $@&quot;\n            SELECT id, event_id, name, price, currency, quantity\n            FROM ticketing.ticket_types\n            WHERE id = {id}\n            FOR UPDATE NOWAIT&quot;) // PostgreSQL: Lock or fail immediately\n        .SingleAsync();\n}\n</code></pre>\n<p>Understanding the magic:</p>\n<ul>\n<li><code>FOR UPDATE NOWAIT</code>: This is the heart of pessimistic locking in PostgreSQL.\nIt tells the database &quot;Grab this row, lock it for me, and if it's already locked, raise an error right now.&quot;</li>\n<li><strong>Error Handling</strong>: We'd wrap our <code>GetWithLockAsync</code> call in a <code>try-catch</code> block to gracefully handle locking failures, either retrying or notifying the user.</li>\n</ul>\n<p>Since there isn't a built-in way in EF Core to add query hints, we have to write raw SQL queries.\nWe can use the PostgreSQL <code>SELECT FOR UPDATE</code> statement to acquire a row-level lock on the selected rows.\nAny competing transactions will be blocked until the current transaction releases the lock.\nThis is a very simple way to implement pessimistic locking.</p>\n<h2>Flavors of Locking and When to Use Them</h2>\n<p>To prevent the operation from waiting for other transactions to release any locked rows, you can combine <code>FOR UPDATE</code> with:</p>\n<ul>\n<li><code>NO WAIT</code> - Reports an error if the row can't be locked instead of waiting.</li>\n<li><code>SKIP LOCKED</code> - Skips any selected rows that cannot be locked.</li>\n</ul>\n<p>Skipping locked rows comes with a caveat - you will get inconsistent results from the database.\nHowever, this can be useful to avoid lock contention when multiple consumers access a queue-like table.\nImplementing the <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\">Outbox pattern</a> is a great example of this.</p>\n<p><strong>SQL Server</strong>: You'd use the <code>WITH (UPDLOCK, READPAST)</code> query hint for a similar effect.</p>\n<h2>Pessimistic Locking vs. Serializable Transactions</h2>\n<p><strong>Serializable</strong> transactions offer the highest level of data consistency.\nThey guarantee that all transactions are executed as if they happened in a strict, sequential order, even if they occur simultaneously.\nThis eliminates the possibility of anomalies like dirty reads (seeing uncommitted data) or non-repeatable reads (data changing between reads).</p>\n<p>Here's how it works:</p>\n<ul>\n<li>When a transaction starts under the Serializable isolation level, the database locks all the data the transaction might access.</li>\n<li>These locks are held until the entire transaction is committed or rolled back.</li>\n<li>Any other transaction attempting to access the locked data will be blocked until the first transaction releases its locks.</li>\n</ul>\n<p>While Serializable transactions provide the ultimate isolation, they come with a significant cost:</p>\n<ul>\n<li><strong>Performance Overhead</strong>: Locking a large chunk of data can severely impact performance,\nespecially in high-concurrency scenarios.</li>\n<li><strong>Deadlocks</strong>: With so much locking happening, there's a higher risk of deadlocks.\nThese occur when two or more transactions are waiting for locks held by each other, creating a stalemate.</li>\n</ul>\n<p>Pessimistic locking with <code>SELECT FOR UPDATE</code> offers a more targeted approach to data isolation.\nYou explicitly lock the specific rows you need to modify.\nOther transactions attempting to access the locked rows are blocked until the lock is released.</p>\n<p>By locking only the necessary data, pessimistic locking avoids the performance overhead associated with locking everything.\nSince you're locking fewer resources, the chances of deadlocks are lower.</p>\n<h2>When to Use Each Approach</h2>\n<p>The best approach depends on your specific needs:</p>\n<ul>\n<li><strong>Serializable Transactions</strong>: Ideal for scenarios involving highly sensitive data where even the slightest inconsistency is unacceptable.\nExamples include financial transactions and medical record updates.</li>\n<li><strong>Pessimistic Locking</strong>: A great choice for most use cases, especially in high-traffic applications.\nIt provides strong consistency while maintaining good performance and reducing deadlock risks.</li>\n</ul>\n<h2>Takeaway</h2>\n<p>I hope this exploration of pessimistic locking has been helpful.\nIt's a powerful tool to have in your arsenal if you have scenarios where absolute data consistency is paramount.</p>\n<p>Both <strong>Serializable transactions</strong> and pessimistic locking with <code>SELECT FOR UPDATE</code> are excellent options for ensuring data consistency.\nConsider the level of isolation required, potential performance impact, and the likelihood of deadlocks when making your choice.</p>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core",
            "title": "A Clever Way To Implement Pessimistic Locking in EF Core",
            "summary": "Sometimes, especially in high-traffic scenarios, you need to ensure only one process can modify a piece of data at a time.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_085.png",
            "date_modified": "2024-04-13T00:00:00.000Z",
            "date_published": "2024-04-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/master-claims-transformation-for-flexible-aspnetcore-authorization",
            "content_html": "<p>Claims transformation modifies the claims of the current <code>ClaimsPrincipal</code> before ASP.NET Core uses them for authorization.\nIt bridges the gap between the claims your identity provider issues in the access token and the claims your application needs for its internal authorization logic.\nYou implement it with the <code>IClaimsTransformation</code> interface.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims\">Claims-based authorization</a> mechanisms are central to modern authorization in ASP.NET Core.\nHowever, the access tokens issued by your Identity Provider (IDP) might not always perfectly align with your application's internal authorization needs.</p>\n<p>External IDPs like <a href=\"https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id\">Microsoft Entra ID</a> (previously Azure AD) or\n<a href=\"https://auth0.com\">Auth0</a> might have their own schema for claims or might not directly issue all the claims your application needs for its authorization logic.</p>\n<p>The solution? Claims transformation.</p>\n<p>Claims transformation allows you to modify the claims before the application uses them for authorization.</p>\n<p>In today's issue, we will:</p>\n<ul>\n<li>Explore the concept of claims transformation in ASP.NET Core</li>\n<li>Explore the <code>IClaimsTransformation</code> interface with practical examples</li>\n<li>Address considerations for security and RBAC (Role-Based Access Control)</li>\n</ul>\n<h2>How Does Claims Transformation Work?</h2>\n<p>They say a picture is worth a thousand words.\nIn software engineering, we have something called <a href=\"https://en.wikipedia.org/wiki/Unified_Modeling_Language\">UML</a> diagrams that we can use to paint a picture.</p>\n<p>Here's a <a href=\"https://en.wikipedia.org/wiki/Sequence_diagram\">sequence diagram</a> showing the claims transformation flow:</p>\n<ol>\n<li>The user authenticates with the Identity Provider</li>\n<li>The user calls the backend API and provides an access token</li>\n<li>The backend API performs claims transformation and authorization</li>\n<li>If the user is correctly authorized, the backend API returns a response</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_084/claims_transformation_sequence_diagram.png\" alt=\"Claims transformation sequence diagram.\">\n<p>Let's see how to implement this in ASP.NET Core.</p>\n<h2>Simple Claims Transformation</h2>\n<p>Claims can be created from any user or identity data issued by a trusted identity provider.\nA claim is a name-value pair that represents the subject's identity, not what the subject can do.</p>\n<p>The core of <a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims#extend-or-add-custom-claims-using-iclaimstransformation\">claims transformation</a>\nin ASP.NET Core is the <a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.iclaimstransformation\"><code>IClaimsTransformation</code></a> interface.</p>\n<p>It exposes a single method to transform claims:</p>\n<pre><code class=\"language-csharp\">public interface IClaimsTransformation\n{\n    Task&lt;ClaimsPrincipal&gt; TransformAsync(ClaimsPrincipal principal);\n}\n</code></pre>\n<p>Here's a simple example of using <code>IClaimsTransformation</code> to add a custom claim:</p>\n<pre><code class=\"language-csharp\">internal static class CustomClaims\n{\n    internal const string CardType = &quot;card_type&quot;;\n}\n\ninternal sealed class CustomClaimsTransformation : IClaimsTransformation\n{\n    public Task&lt;ClaimsPrincipal&gt; TransformAsync(ClaimsPrincipal principal)\n    {\n        if (principal.HasClaim(claim =&gt; claim.Type == CustomClaims.CardType))\n        {\n            return Task.FromResult(principal);\n        }\n\n        ClaimsIdentity claimsIdentity = new ClaimsIdentity();\n\n        claimsIdentity.AddClaim(new Claim(CustomClaims.CardType, &quot;platinum&quot;));\n\n        principal.AddIdentity(claimsIdentity);\n\n        return Task.FromResult(principal);\n    }\n}\n</code></pre>\n<p>The <code>CustomClaimsTransformation</code> class should be registered as a service:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddTransient&lt;IClaimsTransformation, CustomClaimsTransformation&gt;();\n</code></pre>\n<p>Finally, you can define a custom authorization policy that uses this claim:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddAuthorization(options =&gt;\n{\n    options.AddPolicy(\n        &quot;HasPlatinumCard&quot;,\n        builder =&gt; builder\n            .RequireAuthenticatedUser()\n            .RequireClaim(CustomClaims.CardType, &quot;platinum&quot;));\n});\n</code></pre>\n<p>There are a few caveats with using <code>IClaimsTransformation</code> you should be aware of:</p>\n<ul>\n<li><strong>Might execute multiple times</strong>: The <code>TransformAsync</code> method might get called multiple times.\nClaims transformation should be idempotent to avoid adding the same claim multiple times to the <code>ClaimsPrincipal</code>.</li>\n<li><strong>Potential performance impact</strong>: Since it's executed on authentication requests, be mindful of your transformation logic's performance,\nespecially if it involves external calls (database, APIs). Consider caching where appropriate.</li>\n</ul>\n<h2>Implementing RBAC With Claims Transformation</h2>\n<p><a href=\"https://auth0.com/docs/manage-users/access-control/rbac\">Role-Based Access Control (RBAC)</a> is an authorization model where permissions are assigned to roles,\nand users are granted roles.\nClaims transformation helps implement RBAC smoothly.\nBy adding role claims and potentially permission claims, authorization logic throughout your application can be simplified.\nAnother benefit is that you can keep the access token smaller and free of any role or permission claims.</p>\n<p>Let's consider a scenario where your application manages resources at a granular level,\nbut your identity provider only provides coarse-grained roles like <code>Registered</code> or <code>Member</code>.\nYou could use claims transformation to map the <code>Member</code> role to specific fine-grained permissions like <code>SubmitOrder</code> and <code>PurchaseTicket</code>.</p>\n<p>Here's a more complex <code>CustomClaimsTransformation</code> implementation.\nWe send a database query using <code>GetUserPermissionsQuery</code> and get the <code>PermissionsResponse</code> back.\nThe <code>PermissionsResponse</code> contains the user's permissions, which are added as custom claims.</p>\n<pre><code class=\"language-csharp\">internal sealed class CustomClaimsTransformation(\n    IServiceProvider serviceProvider)\n    : IClaimsTransformation\n{\n    public async Task&lt;ClaimsPrincipal&gt; TransformAsync(\n        ClaimsPrincipal principal)\n    {\n        if (principal.HasClaim(c =&gt; c.Type == CustomClaims.Sub ||\n                                    c.Type == CustomClaims.Permission))\n        {\n            return principal;\n        }\n\n        using IServiceScope scope = serviceProvider.CreateScope();\n\n        ISender sender = scope.ServiceProvider.GetRequiredService&lt;ISender&gt;();\n\n        string identityId = principal.GetIdentityId();\n\n        Result&lt;PermissionsResponse&gt; result = await sender.Send(\n            new GetUserPermissionsQuery(identityId));\n\n        if (result.IsFailure)\n        {\n            throw new ClaimsAuthorizationException(\n                nameof(GetUserPermissionsQuery), result.Error);\n        }\n\n        var claimsIdentity = new ClaimsIdentity();\n\n        claimsIdentity.AddClaim(\n            new Claim(CustomClaims.Sub, result.Value.UserId.ToString()));\n\n        foreach (string permission in result.Value.Permissions)\n        {\n            claimsIdentity.AddClaim(\n                new Claim(CustomClaims.Permission, permission));\n        }\n\n        principal.AddIdentity(claimsIdentity);\n\n        return principal;\n    }\n}\n</code></pre>\n<p>Now that the <code>ClaimsPrincipal</code> contains the permissions as custom claims, you can do some interesting things.\nFor example, you can implement a permission-based <code>AuthorizationHandler</code>:</p>\n<pre><code class=\"language-csharp\">internal sealed class PermissionAuthorizationHandler\n    : AuthorizationHandler&lt;PermissionRequirement&gt;\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        PermissionRequirement requirement)\n    {\n        HashSet&lt;string&gt; permissions = context.User.GetPermissions();\n\n        if (permissions.Contains(requirement.Permission))\n        {\n            context.Succeed(requirement);\n        }\n\n        return Task.CompletedTask;\n    }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Claims transformation is an elegant way to bridge the gap between claims provided by identity providers and the needs of your ASP.NET Core application.\nThe <code>IClaimsTransformation</code> interface enables you to customize the claims of the current <code>ClaimsPrincipal</code>.\nWhether you need to add roles, map external groups to internal permissions, or extract additional information from a user profile,\nclaims transformation offers the flexibility to do so.</p>\n<p>However, it's important to use claims transformation with a few key considerations in mind:</p>\n<ul>\n<li>Claims transformations are executed on each request.</li>\n<li>The <code>IClaimsTransformation</code> should be idempotent. It should not add existing claims to the <code>ClaimsPrincipal</code> if executed multiple times.</li>\n<li>Design your transformations efficiently, and consider caching the results if you're fetching external data to enrich your claims.</li>\n</ul>\n<p>If you want to see a complete implementation of RBAC in ASP.NET Core, check out this <a href=\"https://www.youtube.com/playlist?list=PLYpjLpq5ZDGtJOHUbv7KHuxtYLk1nJPw5\">Authentication &amp; Authorization playlist</a>.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/master-claims-transformation-for-flexible-aspnetcore-authorization",
            "title": "Master Claims Transformation for Flexible ASP.NET Core Authorization",
            "summary": "Claims-based authorization mechanisms are central to modern authorization in ASP.NET Core.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_084.png",
            "date_modified": "2024-04-06T00:00:00.000Z",
            "date_published": "2024-04-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing",
            "content_html": "<p>You can horizontally scale an ASP.NET Core API by running multiple instances behind YARP, Microsoft's reverse proxy library, and letting a load balancing policy like <code>RoundRobin</code> distribute the traffic.\nIn the k6 test from this issue, going from 1 to 5 API instances cut the average request duration from 9.68 ms to 4.65 ms and raised throughput from 2260 to 3881 requests per second.</p>\n<p>Modern web applications need to serve increasing numbers of users and handle surges in traffic.\nWhen a single server reaches its limits, performance degrades, leading to slow response times, errors, or complete downtime.</p>\n<p><strong>Load balancing</strong> is a key technique to address these challenges and improve the scalability of your application.</p>\n<p>In this article, we will explore:</p>\n<ul>\n<li>How to use <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\">YARP (Yet Another Reverse Proxy)</a> to implement load balancing</li>\n<li>How to leverage horizontal scaling for performance gains</li>\n<li>How to utilize K6 as a load testing tool</li>\n</ul>\n<p>We'll dive into load balancing, why it matters, and how YARP simplifies the process for .NET applications.</p>\n<h2>Types of Software Scalability</h2>\n<p>Before exploring YARP and load balancing further, let's cover the fundamentals of scaling.</p>\n<p>There are two main approaches:</p>\n<ul>\n<li><strong>Vertical Scaling</strong>: Involves upgrading individual servers with more powerful hardware - more CPU cores, RAM, and faster storage.\nHowever, this has a few limitations: costs escalate quickly, and you'll still hit a performance ceiling.</li>\n<li><strong>Horizontal Scaling</strong>: Involves adding more servers to your infrastructure and distributing the load intelligently among them.\nThis approach offers greater scalability potential, as you can continue adding servers to handle more traffic.</li>\n</ul>\n<p>Horizontal scaling is where load balancing comes in, and YARP shines bright in this approach.</p>\n<h2>Adding a Reverse Proxy</h2>\n<p><a href=\"https://microsoft.github.io/reverse-proxy/index.html\">YARP</a> is a high-performance reverse proxy library from Microsoft.\nIt's designed with modern microservice architectures in mind.\nA <strong>reverse proxy</strong> sits in front of your backend servers, acting as a traffic director.</p>\n<p>Setting up YARP is quite straightforward.\nYou'll install the YARP NuGet package, create a basic configuration to define your backend destinations, and then activate the YARP middleware.\nYARP allows you to perform routing and transformation tasks on incoming requests before they reach your backend servers.</p>\n<p>First, let's install the <code>Yarp.ReverseProxy</code> NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package Yarp.ReverseProxy\n</code></pre>\n<p>Then, we're going to configure the required application services and introduce the YARP middleware to the request pipeline:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddReverseProxy()\n    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;));\n\nvar app = builder.Build();\n\napp.MapReverseProxy();\n\napp.Run();\n</code></pre>\n<p>All that's left is adding the YARP configuration to our <code>appsettings.json</code> file.\nYARP uses <code>Routes</code> to represent incoming requests to the reverse proxy and <code>Clusters</code> to define the downstream services.\nThe <code>{**catch-all}</code> pattern allows us to easily route all incoming requests.</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;api-route&quot;: {\n        &quot;ClusterId&quot;: &quot;api-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [{ &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }]\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;api-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://api:8080&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<p>This configures YARP as a pass-through proxy, but let's update it to support horizontal scaling.</p>\n<h2>Scaling Out With YARP Load Balancing</h2>\n<p>The core of horizontal scaling with YARP lies in its various <a href=\"https://microsoft.github.io/reverse-proxy/articles/load-balancing.html\">load balancing strategies</a>:</p>\n<ul>\n<li><code>PowerOfTwoChoices</code>: Selects two random destinations and selects the one with the least assigned requests.</li>\n<li><code>FirstAlphabetical</code>: Selects the alphabetically first available destination server.</li>\n<li><code>LeastRequests</code>: Sends requests to servers with the least assigned requests.</li>\n<li><code>RoundRobin</code>: Distributes requests evenly across backend servers.</li>\n<li><code>Random</code>: Randomly selects a backend server for each request.</li>\n</ul>\n<p>You configure these strategies within YARP's configuration file.\nThe load balancing strategy can be configured using the <code>LoadBalancingPolicy</code> property on the cluster.</p>\n<p>Here's what the updated YARP configuration looks like with <code>RoundRobin</code> load balancing:</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;api-route&quot;: {\n        &quot;ClusterId&quot;: &quot;api-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [{ &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }]\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;api-cluster&quot;: {\n        &quot;LoadBalancingPolicy&quot;: &quot;RoundRobin&quot;,\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;http://api-1:8080&quot;\n          },\n          &quot;destination2&quot;: {\n            &quot;Address&quot;: &quot;http://api-2:8080&quot;\n          },\n          &quot;destination3&quot;: {\n            &quot;Address&quot;: &quot;http://api-3:8080&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<p>Here's a diagram of what our system could look like with a YARP load balancer and horizontally scaled application servers.</p>\n<p>The incoming API requests will first hit YARP, distributing the traffic to the application servers based on the load balancing strategy.\nIn this example, there's one database serving multiple application instances.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_083/horizontal_scaling.png\" alt=\"Horizontal scaling with a YARP load balancer.\">\n<p>And now, let's do some performance testing.</p>\n<h2>Performance Testing with K6</h2>\n<p>To see the impact of our horizontal scaling efforts, we need to do some <strong>load testing</strong>.\n<a href=\"https://k6.io/\">K6</a> is a modern, developer-friendly load testing tool.\nWe'll write K6 scripts to simulate user traffic on our application and compare metrics like average response time and the number of successful requests per second.</p>\n<p>The application we're going to scale horizontally has two API endpoints.\nThe <code>POST /users</code> endpoint creates a new user, saves the user to a <a href=\"https://www.postgresql.org/\">PostgreSQL</a> database, and returns the user's identifier.\nThe <code>GET /users/id</code> endpoint returns a user with the given identifer if it exists.</p>\n<p>Here's a k6 performance test that will:</p>\n<ul>\n<li>Ramp up to <strong>20 virtual users</strong></li>\n<li>Send a <code>POST</code> request to the <code>/users</code> endpoint</li>\n<li>Check that the response is <code>201 Created</code></li>\n<li>Send a <code>GET</code> request to the <code>/users/{id}</code> endpoint</li>\n<li>Check that the response is <code>200 OK</code></li>\n</ul>\n<p>Note that all API requests go through the YARP load balancer.</p>\n<pre><code class=\"language-js\">import { check } from 'k6';\nimport http from 'k6/http';\n\nexport const options = {\n  stages: [\n    { duration: '10s', target: 20 },\n    { duration: '1m40s', target: 20 },\n    { duration: '10s', target: 0 }\n  ]\n};\n\nexport default function () {\n  const proxyUrl = 'http://localhost:3000';\n\n  const response = http.post(`${proxyUrl}/users`);\n\n  check(response, {\n    'response code was 201': (res) =&gt; res.status == 201\n  });\n\n  const userResponse = http.get(`${proxyUrl}/users/${response.body}`);\n\n  check(userResponse, {\n    'response code was 200': (res) =&gt; res.status == 200\n  });\n}\n</code></pre>\n<p>To make the performance testing results more consistent, we can limit the available resources on the <a href=\"https://www.docker.com\">Docker</a> containers to <code>1 CPU</code> and <code>0.5G</code> of RAM.</p>\n<pre><code class=\"language-yml\">services:\n  api:\n    image: ${DOCKER_REGISTRY-}loadbalancingapi\n    cpus: 1\n    mem_limit: '0.5G'\n    ports:\n      - 5000:8080\n    networks:\n      - proxybackend\n</code></pre>\n<p>Finally, here are the k6 performance testing results:</p>\n<pre><code>|    API Instances   |     Request Duration     |    Requests Per Second    |\n|------------------- |------------------------- |---------------------------:\n|         1          |         9.68 ms          |          2260/s           |\n|--------------------|------------------------- |---------------------------|\n|         2          |         6.57 ms          |          2764/s           |\n|--------------------|------------------------- |---------------------------|\n|         3          |         5.62 ms          |          3227/s           |\n|--------------------|------------------------- |---------------------------|\n|         5          |         4.65 ms          |          3881/s           |\n</code></pre>\n<h2>Summary</h2>\n<p>Horizontal scaling, coupled with effective load balancing, can significantly enhance the performance and scalability of your web applications.\nThe benefits of horizontal scaling become especially apparent in high-traffic scenarios where a single server can no longer cope with the demand.</p>\n<p>YARP is a powerful and user-friendly reverse proxy server for .NET applications.\nHowever, highly complex, large-scale distributed systems might benefit from specialized, standalone load-balancing solutions.\nThese dedicated solutions can offer more granular control and sophisticated features.</p>\n<p>If you want to learn more, here's how to <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\">build an API Gateway using YARP</a>.</p>\n<p>You can find the <a href=\"https://github.com/m-jovanovic/yarp-load-balancing\">source code</a> for this example on GitHub.</p>\n<p>That's all for today. I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing",
            "title": "Horizontally Scaling ASP.NET Core APIs With YARP Load Balancing",
            "summary": "When a single server reaches its limits, performance degrades, leading to slow response times, errors, or complete downtime.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_083.png",
            "date_modified": "2024-03-30T00:00:00.000Z",
            "date_published": "2024-03-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/fast-sql-bulk-inserts-with-csharp-and-ef-core",
            "content_html": "<p>The fastest way to bulk insert with EF Core depends on your requirements.\nEFCore.BulkExtensions and Entity Framework Extensions bypass EF's change tracker and generate optimized SQL, while <code>SqlBulkCopy</code> is typically the fastest option for SQL Server.\nIn this issue, I benchmark them against Dapper and EF Core's built-in batching, from 100 to 1,000,000 records.</p>\n<p>Whether you're building a data analytics platform, migrating a legacy system, or onboarding a surge of new users,\nthere will likely come a time when you'll need to insert a massive amount of data into your database.</p>\n<p>Inserting the records one by one feels like watching paint dry in slow motion.\nTraditional methods won't cut it.</p>\n<p>So, understanding fast bulk insert techniques with C# and EF Core becomes essential.</p>\n<p>In today's issue, we'll explore several options for performing bulk inserts in C#:</p>\n<ul>\n<li>Dapper</li>\n<li>EF Core</li>\n<li>EF Core Bulk extensions</li>\n<li>SQL Bulk Copy</li>\n<li>Entity Framework Extensions</li>\n</ul>\n<p>The examples are based on a <code>User</code> class with a respective <code>Users</code> table in <strong>SQL Server</strong>.</p>\n<pre><code class=\"language-csharp\">public class User\n{\n    public int Id { get; set; }\n    public string Email { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public string PhoneNumber { get; set; }\n}\n</code></pre>\n<p>This isn't a complete list of bulk insert implementations.\nThere are a few options I didn't explore, like manully generating SQL statements\nand using <a href=\"https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine?view=sql-server-ver16\">Table-Valued parameters</a>.</p>\n<h2>What Is EF Core Bulk Insert?</h2>\n<p>When working with large datasets, standard EF Core patterns quickly become a bottleneck.\nUsing <code>Add</code> and <code>SaveChanges</code> for thousands of records means EF tracks every entity in memory\nand generates individual SQL <code>INSERT</code> statements, each requiring its own round trip to the database.</p>\n<p>EF Core bulk insert techniques solve this by taking one of two approaches:\neither batching many <code>INSERT</code> statements into a single network round trip (the built-in <code>AddRange</code>),\nor bypassing EF's change tracker entirely and streaming data using the database's native bulk copy protocol\n(like SQL Server's <code>SqlBulkCopy</code>).\nThe result can reduce insert time from minutes to seconds for large datasets.</p>\n<h2>EF Core Simple Approach</h2>\n<p>Let's start with a simple example using EF Core.\nWe're creating an <code>ApplicationDbContext</code> instance, adding a <code>User</code> object, and calling <code>SaveChangesAsync</code>.\nThis will insert each record to the database one by one.\nIn other words, each record requires one round trip to the database.</p>\n<pre><code class=\"language-csharp\">using var context = new ApplicationDbContext();\n\nforeach (var user in GetUsers())\n{\n    context.Users.Add(user);\n\n    await context.SaveChangesAsync();\n}\n</code></pre>\n<p>The results are as poor as you'd expect:</p>\n<pre><code>EF Core - Add one and save, for 100 users: 20 ms\nEF Core - Add one and save, for 1,000 users: 260 ms\nEF Core - Add one and save, for 10,000 users: 8,860 ms\n</code></pre>\n<p>I omitted the results with <code>100,000</code> and <code>1,000,000</code> records because they took too long to execute.</p>\n<p>We'll use this as a &quot;how not to do bulk inserts&quot; example.</p>\n<h2>Dapper Simple Insert</h2>\n<p><a href=\"https://github.com/DapperLib/Dapper\">Dapper</a> is a simple SQL-to-object mapper for .NET.\nIt allows us to easily insert a collection of objects into the database.</p>\n<p>I'm using Dapper's feature to unwrap a collection into a SQL <code>INSERT</code> statement.</p>\n<pre><code class=\"language-csharp\">using var connection = new SqlConnection(connectionString);\nconnection.Open();\n\nconst string sql =\n    @&quot;\n    INSERT INTO Users (Email, FirstName, LastName, PhoneNumber)\n    VALUES (@Email, @FirstName, @LastName, @PhoneNumber);\n    &quot;;\n\nawait connection.ExecuteAsync(sql, GetUsers());\n</code></pre>\n<p>The results are much better than the initial example:</p>\n<pre><code>Dapper - Insert range, for 100 users: 10 ms\nDapper - Insert range, for 1,000 users: 113 ms\nDapper - Insert range, for 10,000 users: 1,028 ms\nDapper - Insert range, for 100,000 users: 10,916 ms\nDapper - Insert range, for 1,000,000 users: 109,065 ms\n</code></pre>\n<h2>EF Core Add and Save</h2>\n<p>However, EF Core still didn't throw in the towel.\nThe first example was poorly implemented on purpose.\nEF Core can batch multiple SQL statements together, so let's use that.</p>\n<p>If we make a simple change, we can get significantly better performance.\nFirst, we're adding all the objects to the <code>ApplicationDbContext</code>.\nThen, we're going to call <code>SaveChangesAsync</code> only once.</p>\n<p>EF will create a batched SQL statement - group many <code>INSERT</code> statements together - and send them to the database together.\nThis reduces the number of round trips to the database, giving us improved performance.</p>\n<pre><code class=\"language-csharp\">using var context = new ApplicationDbContext();\n\nforeach (var user in GetUsers())\n{\n    context.Users.Add(user);\n}\n\nawait context.SaveChangesAsync();\n</code></pre>\n<p>Here are the benchmark results of this implementation:</p>\n<pre><code>EF Core - Add all and save, for 100 users: 2 ms\nEF Core - Add all and save, for 1,000 users: 18 ms\nEF Core - Add all and save, for 10,000 users: 203 ms\nEF Core - Add all and save, for 100,000 users: 2,129 ms\nEF Core - Add all and save, for 1,000,000 users: 21,557 ms\n</code></pre>\n<p>Remember, it took Dapper <strong>109 seconds</strong> to insert <code>1,000,000</code> records.\nWe can achieve the same with EF Core batched queries in <strong>~21 seconds</strong>.</p>\n<h2>EF Core AddRange and Save</h2>\n<p>This is an alternative to the previous example.\nInstead of calling <code>Add</code> for all objects, we can call <code>AddRange</code> and pass in a collection.</p>\n<p>I wanted to show this implementation because I prefer it over the previous one.</p>\n<pre><code class=\"language-csharp\">using var context = new ApplicationDbContext();\n\ncontext.Users.AddRange(GetUsers());\n\nawait context.SaveChangesAsync();\n</code></pre>\n<p>The results are very similar to the previous example:</p>\n<pre><code>EF Core - Add range and save, for 100 users: 2 ms\nEF Core - Add range and save, for 1,000 users: 18 ms\nEF Core - Add range and save, for 10,000 users: 204 ms\nEF Core - Add range and save, for 100,000 users: 2,111 ms\nEF Core - Add range and save, for 1,000,000 users: 21,605 ms\n</code></pre>\n<h2>EF Core Bulk Insert with EF Core Bulk Extensions</h2>\n<p>There's an awesome library called <a href=\"https://github.com/borisdj/EFCore.BulkExtensions\">EF Core Bulk Extensions</a> that we can use to squeeze out more performance.\nYou can do a lot more than bulk inserts with this library, so it's worth exploring.\nThis library is open source, and has a community license if you meet the free usage criteria.\nCheck the <a href=\"https://github.com/borisdj/EFCore.BulkExtensions?#license\">licensing section</a> for more details.</p>\n<p>For our use case, the <code>BulkInsertAsync</code> method is an excellent choice.\nWe can pass the collection of objects, and it will perform an SQL bulk insert.</p>\n<pre><code class=\"language-csharp\">using var context = new ApplicationDbContext();\n\nawait context.BulkInsertAsync(GetUsers());\n</code></pre>\n<p>The performance is equally amazing:</p>\n<pre><code>EF Core - Bulk Extensions, for 100 users: 1.9 ms\nEF Core - Bulk Extensions, for 1,000 users: 8 ms\nEF Core - Bulk Extensions, for 10,000 users: 76 ms\nEF Core - Bulk Extensions, for 100,000 users: 742 ms\nEF Core - Bulk Extensions, for 1,000,000 users: 8,333 ms\n</code></pre>\n<p>For comparison, we needed <strong>~21 seconds</strong> to insert <code>1,000,000</code> records with EF Core batched queries.\nWe can do the same with the <a href=\"https://github.com/borisdj/EFCore.BulkExtensions\">Bulk Extensions</a> library in just <strong>8 seconds</strong>.</p>\n<h2>SQL Bulk Insert with SqlBulkCopy</h2>\n<p>If we can't get the desired performance from EF Core, we can try using <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlbulkcopy\"><code>SqlBulkCopy</code></a>.\nSQL Server supports <a href=\"https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/bulk-copy-operations-in-sql-server\">bulk copy operations</a> natively, so let's use this.</p>\n<p>This implementation is slightly more complex than the EF Core examples.\nWe need to configure the <code>SqlBulkCopy</code> instance and create a <code>DataTable</code> containing the objects we want to insert.</p>\n<pre><code class=\"language-csharp\">using var bulkCopy = new SqlBulkCopy(ConnectionString);\n\nbulkCopy.DestinationTableName = &quot;dbo.Users&quot;;\n\nbulkCopy.ColumnMappings.Add(nameof(User.Email), &quot;Email&quot;);\nbulkCopy.ColumnMappings.Add(nameof(User.FirstName), &quot;FirstName&quot;);\nbulkCopy.ColumnMappings.Add(nameof(User.LastName), &quot;LastName&quot;);\nbulkCopy.ColumnMappings.Add(nameof(User.PhoneNumber), &quot;PhoneNumber&quot;);\n\nawait bulkCopy.WriteToServerAsync(GetUsersDataTable());\n</code></pre>\n<p>However, the performance is blazing fast:</p>\n<pre><code>SQL Bulk Copy, for 100 users: 1.7 ms\nSQL Bulk Copy, for 1,000 users: 7 ms\nSQL Bulk Copy, for 10,000 users: 68 ms\nSQL Bulk Copy, for 100,000 users: 646 ms\nSQL Bulk Copy, for 1,000,000 users: 7,339 ms\n</code></pre>\n<p>Here's how you can create a <code>DataTable</code> and populate it with a list of objects:</p>\n<pre><code class=\"language-csharp\">DataTable GetUsersDataTable()\n{\n    var dataTable = new DataTable();\n\n    dataTable.Columns.Add(nameof(User.Email), typeof(string));\n    dataTable.Columns.Add(nameof(User.FirstName), typeof(string));\n    dataTable.Columns.Add(nameof(User.LastName), typeof(string));\n    dataTable.Columns.Add(nameof(User.PhoneNumber), typeof(string));\n\n    foreach (var user in GetUsers())\n    {\n        dataTable.Rows.Add(\n            user.Email, user.FirstName, user.LastName, user.PhoneNumber);\n    }\n\n    return dataTable;\n}\n</code></pre>\n<h2>Entity Framework Bulk Insert with EF Core Extensions</h2>\n<p>Can we do better than <code>SqlBulkCopy</code>?</p>\n<p>Maybe, at least my benchmark results suggest that we can.</p>\n<p>There's another awesome library called <a href=\"https://entityframework-extensions.net/?utm_source=milanjovanovic&utm_medium=newsletter\">Entity Framework Extensions</a>.\nIt's much more than just a bulk insert library - so I highly recommend checking it out.\nHowever, we'll use it for bulk inserts today.</p>\n<p>For our use case, the <code>BulkInsertOptimizedAsync</code> method is an excellent choice.\nWe can pass the collection of objects, and it will perform an SQL bulk insert.\nIt'll also do some optimizations under the hood to improve performance.</p>\n<pre><code class=\"language-csharp\">using var context = new ApplicationDbContext();\n\nawait context.BulkInsertOptimizedAsync(GetUsers());\n</code></pre>\n<p>The performance is nothing short of amazing:</p>\n<pre><code>EF Core - Entity Framework Extensions, for 100 users: 1.86 ms\nEF Core - Entity Framework Extensions, for 1,000 users: 6.9 ms\nEF Core - Entity Framework Extensions, for 10,000 users: 66 ms\nEF Core - Entity Framework Extensions, for 100,000 users: 636 ms\nEF Core - Entity Framework Extensions, for 1,000,000 users: 7,106 ms\n</code></pre>\n<h2>EF Core Bulk Insert vs SqlBulkCopy vs Dapper</h2>\n<p>Not all bulk insert options are equal. Here's a comparison to help you choose the right approach:</p>\n<pre><code>| Method                                                 | Relative Speed | EF Core Integration         | Database Support   | License          |\n| ------------------------------------------------------ | -------------- | --------------------------- | ------------------ | ---------------- |\n| EF Core `AddRange` + `SaveChanges`                     | Medium         | Full (change tracking)      | All EF providers   | Free             |\n| Dapper `ExecuteAsync`                                  | Medium         | None                        | Any ADO.NET        | Free             |\n| EFCore.BulkExtensions `BulkInsertAsync`                | Fast           | Partial (bypasses tracking) | Multiple providers | Free (community) |\n| `SqlBulkCopy`                                          | Fastest        | None                        | SQL Server only    | Free             |\n| Entity Framework Extensions `BulkInsertOptimizedAsync` | Fastest        | Partial (bypasses tracking) | Multiple providers | Commercial       |\n</code></pre>\n<p><strong>EF Core <code>AddRange</code></strong> is the simplest option and works out of the box, but it goes through the <a href=\"https://milanjovanovic.tech/blog/change-tracker-ef-core\"><strong>change tracker</strong></a>, making it slower for large datasets.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a> is a good fit if you're already using it in your stack, but it doesn't integrate with your EF Core context.</p>\n<p><strong>EFCore.BulkExtensions</strong> hits a sweet spot: near-<code>SqlBulkCopy</code> speed, works with your existing <code>DbContext</code>, and is open source.</p>\n<p><strong><code>SqlBulkCopy</code></strong> is the raw speed champion for SQL Server but requires boilerplate <code>DataTable</code> setup and is SQL Server-only.</p>\n<p><strong>Entity Framework Extensions</strong> matches <code>SqlBulkCopy</code> performance with EF Core integration, but requires a commercial license.</p>\n<h2>When Should You Use Each EF Core Bulk Insert Method?</h2>\n<p>Choose the right method based on your scenario:</p>\n<pre><code>| Scenario                                                              | Recommended Method                 |\n| --------------------------------------------------------------------- | ---------------------------------- |\n| Small datasets (under 1,000 rows)                                     | EF Core `AddRange` + `SaveChanges` |\n| Medium datasets with existing Dapper infrastructure                   | Dapper `ExecuteAsync`              |\n| Large datasets, open-source required, EF Core codebase                | `EFCore.BulkExtensions`            |\n| Maximum speed, SQL Server only                                        | `SqlBulkCopy`                      |\n| Maximum speed with EF Core integration, commercial license acceptable | Entity Framework Extensions        |\n| PostgreSQL, MySQL, or other non-SQL Server databases                  | `EFCore.BulkExtensions`            |\n</code></pre>\n<h2>Results</h2>\n<p>Here are the results for all the bulk insert implementations:</p>\n<pre><code>| Method             |   Size     |      Speed\n|------------------- |----------- |----------------:\n| EF_OneByOne        | 100        |      19.800 ms |\n| EF_OneByOne        | 1000       |     259.870 ms |\n| EF_OneByOne        | 10000      |   8,860.790 ms |\n| EF_OneByOne        | 100000     |            N/A |\n| EF_OneByOne        | 1000000    |            N/A |\n\n| Dapper_Insert      | 100        |      10.650 ms |\n| Dapper_Insert      | 1000       |     113.137 ms |\n| Dapper_Insert      | 10000      |   1,027.979 ms |\n| Dapper_Insert      | 100000     |  10,916.628 ms |\n| Dapper_Insert      | 1000000    | 109,064.815 ms |\n\n| EF_AddAll          | 100        |       2.064 ms |\n| EF_AddAll          | 1000       |      17.906 ms |\n| EF_AddAll          | 10000      |     202.975 ms |\n| EF_AddAll          | 100000     |   2,129.370 ms |\n| EF_AddAll          | 1000000    |  21,557.136 ms |\n\n| EF_AddRange        | 100        |       2.035 ms |\n| EF_AddRange        | 1000       |      17.857 ms |\n| EF_AddRange        | 10000      |     204.029 ms |\n| EF_AddRange        | 100000     |   2,111.106 ms |\n| EF_AddRange        | 1000000    |  21,605.668 ms |\n\n| BulkExtensions     | 100        |       1.922 ms |\n| BulkExtensions     | 1000       |       7.943 ms |\n| BulkExtensions     | 10000      |      76.406 ms |\n| BulkExtensions     | 100000     |     742.325 ms |\n| BulkExtensions     | 1000000    |   8,333.950 ms |\n\n| BulkCopy           | 100        |       1.721 ms |\n| BulkCopy           | 1000       |       7.380 ms |\n| BulkCopy           | 10000      |      68.364 ms |\n| BulkCopy           | 100000     |     646.219 ms |\n| BulkCopy           | 1000000    |   7,339.298 ms |\n\n| EF Extensions      | 100        |       1.860 ms |\n| EF Extensions      | 1000       |       6.923 ms |\n| EF Extensions      | 10000      |      68.106 ms |\n| EF Extensions      | 100000     |     636.231 ms |\n| EF Extensions      | 1000000    |   7,106.891 ms |\n</code></pre>\n<h2>Takeaway</h2>\n<p><code>SqlBulkCopy</code> holds the crown for maximum raw speed and simplicity.\nHowever, <a href=\"https://entityframework-extensions.net/?utm_source=milanjovanovic&utm_medium=newsletter\">Entity Framework Extensions</a>\ndeliver fantastic performance while maintaining the ease of use that EF Core is known for.</p>\n<p>The best choice hinges on your project's specific demands:</p>\n<ul>\n<li>Performance is all that matters? <code>SqlBulkCopy</code> is your solution.</li>\n<li>Need excellent speed and streamlined development? EF Core is a smart choice.</li>\n<li>Want a balance between performance and ease of use? Consider using <a href=\"https://entityframework-extensions.net/?utm_source=milanjovanovic&utm_medium=newsletter\">Entity Framework Extensions</a>.</li>\n</ul>\n<p>I leave it up to you to decide which option is best for your use case.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/fast-sql-bulk-inserts-with-csharp-and-ef-core",
            "title": "Fast SQL Bulk Inserts With C# and EF Core",
            "summary": "Explore various methods for fast bulk inserts in SQL with C# and EF Core, highlighting techniques like Dapper, EF Core optimizations, EF Core Bulk Extensions…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_082.png",
            "date_modified": "2024-03-23T00:00:00.000Z",
            "date_published": "2024-03-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core",
            "content_html": "<p>A soft delete marks a record as deleted with a flag like <code>IsDeleted</code> instead of removing it, so the row stays in the database and can be restored later.\nIn EF Core you implement it with a <code>SaveChangesInterceptor</code> that turns deletes into updates, plus a global query filter that hides the flagged rows from every query.</p>\n<p>To delete or not to delete, that is the question (pun intended).</p>\n<p>The traditional way to remove information in a database is through a &quot;hard delete.&quot;\nA hard delete permanently erases a record from the database table.\nWhile this seems straightforward, it presents a significant risk: once that data is gone, it's gone for good.</p>\n<p>Instead of physically removing a record, a soft delete marks it as deleted, usually by setting a flag like <code>IsDeleted</code> to <code>true</code>.\nThe record remains in the database, but it's effectively hidden from regular application queries.</p>\n<p>Today, we'll dive into the details of how to implement soft deletes using EF Core.\nWe'll discuss global query filters, explore efficient ways to handle soft-deleted data, and weigh the trade-offs.</p>\n<h2>What Is a Soft Delete?</h2>\n<p>A soft delete is a data persistence strategy that prevents the permanent deletion of records from your database.\nInstead of removing data from the database, a flag is set on the record, indicating it as &quot;deleted.&quot;</p>\n<p>This approach allows the application to ignore these records during normal queries.\nHowever, you can restore these records if necessary.\nSoft delete is also practical if you want to keep foreign key constraints in place.\nSoft delete is a &quot;non-destructive&quot; operation in contrast with hard delete, where data is completely removed from the database.</p>\n<p>A hard delete uses the SQL <code>DELETE</code> statement:</p>\n<pre><code class=\"language-sql\">DELETE FROM bookings.Reviews\nWHERE Id = @BookingId;\n</code></pre>\n<p>A soft delete, on the other hand, uses an <code>UPDATE</code> statement:</p>\n<pre><code class=\"language-sql\">UPDATE bookings.Reviews\nSET IsDeleted = 1, DeletedOnUtc = @UtcNow\nWHERE Id = @BookingId;\n</code></pre>\n<p>The data is still present in the database, and the operation can be undone.</p>\n<p>But you need to remember to filter out soft-deleted data when querying the database:</p>\n<pre><code class=\"language-sql\">SELECT *\nFROM bookings.Reviews\nWHERE IsDeleted = 0;\n</code></pre>\n<p>Let's see how we can implement soft delete with <a href=\"https://learn.microsoft.com/en-us/ef/core/\">EF Core</a>.</p>\n<h2>Soft Deletes Using EF Core Interceptors</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors\">EF Core interceptors</a>\nprovide a powerful mechanism for intercepting and modifying database operations.\nFor example, you can intercept the saving changes operation to implement soft delete functionality.</p>\n<p>Let's create an <code>ISoftDeletable</code> marker interface to represent soft-deletable entities:</p>\n<pre><code class=\"language-csharp\">public interface ISoftDeletable\n{\n    bool IsDeleted { get; set; }\n\n    DateTime? DeletedOnUtc { get; set; }\n}\n</code></pre>\n<p>The entities that should support soft delete will implement this interface.\nYou will need to apply the respective database migration to create these columns.</p>\n<p>The next component we need is a <code>SaveChangesInterceptor</code>, which allows us to hook into the <code>SavingChangesAsync</code> (or <code>SavingChanges</code>) method.\nWe can access the <code>ChangeTracker</code> and look for entries that implement <code>ISoftDeletable</code> and are flagged for deletion.\nWe can figure this out by checking if the entity state is <code>EntityState.Deleted</code>.</p>\n<p>When we find the entities flagged for deletion, we loop through them and update their state to <code>EntityState.Modified</code>.\nYou should also set the respective values for the <code>IsDeleted</code> and <code>DeletedOnUtc</code> properties.\nThis will cause EF to generate an <code>UPDATE</code> operation instead of a <code>DELETE</code> operation.</p>\n<pre><code class=\"language-csharp\">public sealed class SoftDeleteInterceptor : SaveChangesInterceptor\n{\n    public override ValueTask&lt;InterceptionResult&lt;int&gt;&gt; SavingChangesAsync(\n        DbContextEventData eventData,\n        InterceptionResult&lt;int&gt; result,\n        CancellationToken cancellationToken = default)\n    {\n        if (eventData.Context is null)\n        {\n            return base.SavingChangesAsync(\n                eventData, result, cancellationToken);\n        }\n\n        IEnumerable&lt;EntityEntry&lt;ISoftDeletable&gt;&gt; entries =\n            eventData\n                .Context\n                .ChangeTracker\n                .Entries&lt;ISoftDeletable&gt;()\n                .Where(e =&gt; e.State == EntityState.Deleted);\n\n        foreach (EntityEntry&lt;ISoftDeletable&gt; softDeletable in entries)\n        {\n            softDeletable.State = EntityState.Modified;\n            softDeletable.Entity.IsDeleted = true;\n            softDeletable.Entity.DeletedOnUtc = DateTime.UtcNow;\n        }\n\n        return base.SavingChangesAsync(eventData, result, cancellationToken);\n    }\n}\n</code></pre>\n<p>This approach ensures that all delete operations across the application respect the soft delete policy.</p>\n<p>You'll need to register the <code>SoftDeleteInterceptor</code> with dependency injection and configure it with the <code>ApplicationDbContext</code>.</p>\n<pre><code class=\"language-csharp\">services.AddSingleton&lt;SoftDeleteInterceptor&gt;();\n\nservices.AddDbContext&lt;ApplicationDbContext&gt;(\n    (sp, options) =&gt; options\n        .UseSqlServer(connectionString)\n        .AddInterceptors(\n            sp.GetRequiredService&lt;SoftDeleteInterceptor&gt;()));\n</code></pre>\n<p>If you want to learn more, here's an article with a few practical use cases for <a href=\"https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors\">EF Core interceptors</a>.</p>\n<h2>Automatically Filtering Soft-Deleted Data</h2>\n<p>To ensure that soft-deleted records are automatically excluded from queries, we can use <a href=\"https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core\">EF Core global query filters</a>.\nWe can apply query filters to entities using the <code>OnModelCreating</code> method to automatically exclude records marked as deleted.\nThis feature dramatically simplifies writing queries.</p>\n<p>Here's how to configure the soft delete query filter:</p>\n<pre><code class=\"language-csharp\">public sealed class ApplicationDbContext(\n    DbContextOptions&lt;UsersDbContext&gt; options) : DbContext(options)\n{\n    public DbSet&lt;Review&gt; Reviews { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity&lt;Review&gt;().HasQueryFilter(r =&gt; !r.IsDeleted);\n    }\n}\n</code></pre>\n<p>A limitation is you can't have more than one query filter configured per entity.</p>\n<p>However, it's sometimes useful to explicitly include soft-deleted records.\nYou can achieve this using the <code>IgnoreQueryFilters</code> method.</p>\n<pre><code class=\"language-csharp\">dbContex.Reviews\n    .IgnoreQueryFilters()\n    .Where(r =&gt; r.ApartmentId == apartmentId)\n    .ToList();\n</code></pre>\n<h2>Faster Queries Using Filtered Index</h2>\n<p>To <a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>enhance query performance</strong></a>, especially in tables with a significant number of soft-deleted records, you can use <strong>filtered indexes</strong>.\nA filtered index only includes records that meet the specified criteria.\nThis reduces the index size and improves query execution times for operations that exclude filtered records.\nMost popular databases support filtered indexes.</p>\n<p>Here's how you can configure a <a href=\"https://learn.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations#index-filter\">filtered index with EF Core</a>:</p>\n<pre><code class=\"language-csharp\">public sealed class ApplicationDbContext(\n    DbContextOptions&lt;UsersDbContext&gt; options) : DbContext(options)\n{\n    public DbSet&lt;Review&gt; Reviews { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity&lt;Review&gt;().HasQueryFilter(r =&gt; !r.IsDeleted);\n\n        modelBuilder.Entity&lt;Review&gt;()\n            .HasIndex(r =&gt; r.IsDeleted)\n            .HasFilter(&quot;IsDeleted = 0&quot;);\n    }\n}\n</code></pre>\n<p>The <code>HasFilter</code> method accepts the SQL filter for records that will be included in the index.</p>\n<p>You can also create a filtered index using SQL:</p>\n<pre><code class=\"language-sql\">CREATE INDEX IX_Reviews_IsDeleted\nON bookings.Reviews (IsDeleted)\nWHERE IsDeleted = 0;\n</code></pre>\n<p>You can learn more about filtered indexes from the documentation:</p>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/sql/relational-databases/indexes/create-filtered-indexes?view=sql-server-ver16\">SQL Server filtered index</a></li>\n<li><a href=\"https://www.postgresql.org/docs/16/indexes-partial.html\">PostgreSQL partial index</a></li>\n</ul>\n<h2>Do You Really Need Soft Deletes?</h2>\n<p>It's worthwhile to think through if you even need to soft delete records.</p>\n<p>In enterprise systems, you're typically not thinking about &quot;deleting&quot; data.\nThere are business concepts that don't involve deleting data.\nA few examples are canceling an order, refunding a payment, or voiding an invoice.\nThese &quot;destructive&quot; operations return the system to a previous state.\nBut from a business perspective, you aren't really deleting data.</p>\n<p>Soft deletes are helpful if there is a risk of accidental deletion.\nThey allow you to easily restore soft-deleted records.</p>\n<p>In any case, consider if soft deletes make sense from a business perspective.</p>\n<h2>Takeaway</h2>\n<p>Soft deletes offer a valuable safety net for data recovery and can enhance historical data tracking.\nHowever, it's crucial to assess whether they truly align with your application's specific requirements.\nConsider factors like the importance of deleted data recovery, any auditing needs, and your industry's regulations.\nCreating a filtered index can improve query performance on tables with soft-deleted records.</p>\n<p>If you decide that soft deletes are a good fit, EF Core provides the tools necessary for a streamlined implementation.</p>\n<p>Thanks for reading, and I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core",
            "title": "Implementing Soft Delete With EF Core",
            "summary": "A soft delete is a data persistence strategy that prevents the permanent deletion of records from your database.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_081.png",
            "date_modified": "2024-03-16T00:00:00.000Z",
            "date_published": "2024-03-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/what-is-a-modular-monolith",
            "content_html": "<p>A modular monolith is a single application organized into independent modules, each grouping related functionality behind a clear boundary.\nThe modules stay loosely coupled and communicate through public APIs, while the application still deploys as a single unit.\nYou get high cohesion and data encapsulation without the operational complexity of a distributed system.</p>\n<p>I've worked with many different software architectures over the years.</p>\n<p>There's one that clearly stands out for its benefits: <strong>Modular Monolith architecture</strong>.</p>\n<p>Modular monoliths blend the simplicity and robustness of traditional monolithic applications with the flexibility and scalability of microservices.\nI'm tempted to say they bring together the best of both worlds.</p>\n<p>The modular monolith architecture allows you to work in a unified codebase with clearly defined boundaries and independent modules.\nYou can have a high development velocity without the complexity of distributed systems.</p>\n<p>Today, I'll introduce you to the modular monolith architecture and why you should know about it.</p>\n<h2>What is a Modular Monolith?</h2>\n<p>A <strong>modular monolith</strong> is an architectural pattern that structures the application into independent modules or components with well-defined boundaries.\nThe modules are split based on logical boundaries, grouping together related functionalities.\nThis approach significantly improves the cohesion of the system.</p>\n<p>The modules are loosely coupled, which further promotes modularity and separation of concerns.\nModules communicate through a public API, and you can learn more about this in my article on <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\">modular monolith communication patterns</a>.</p>\n<p>But what are the benefits of a modular design?</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_080/modular_monolith.png\" alt=\"Modular monolith.\">\n<p>If we take the example of an apartment booking system illustrated above.\nDuring the holiday season, the system is expecting a traffic spike.\nThe bookings and payments modules need to scale so they can be deployed independently.\nAt the end of the holiday season, they can be merged back into a single deployment.\nModular monoliths give you this kind of flexibility.</p>\n<h2>Modular Architecture</h2>\n<p>Modular monoliths introduce a few important technical challenges that we will need to solve.</p>\n<p>To achieve a modular architecture, the modules:</p>\n<ul>\n<li>Must be independent and interchangeable</li>\n<li>Must be able to provide the required functionality</li>\n<li>Must have a well-defined interface exposed to other modules</li>\n</ul>\n<p>Is it possible for a module to be completely independent?\nNot really.\nThat would mean it's not integrated with other modules.\nWe want loosely coupled modules and to keep the number of dependencies low.\nWe can use a few techniques to keep the modules independent, and having good <a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\">data isolation</a> is one example.</p>\n<p>Another factor you need to consider is how strong the dependency is.\nIf two modules are very &quot;chatty&quot;, you might have incorrectly defined the boundaries.\nYou should consider merging these modules together.</p>\n<p>Remember, a module is a grouping of related functionalities accessed via a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\">well-defined interface</a>.</p>\n<p>Having a modular architecture allows you to easily extract modules into separate services.</p>\n<h2>Monolith First</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>Microservices</strong></a> have become the most popular architectural pattern in recent years, and for good reason.\nMicroservices offer many benefits like clearly defined service boundaries, independent deployments, independent scalability, and much more.</p>\n<p>However, most teams would be better off starting with a monolith application.</p>\n<p>A monolith is an architectural pattern where all components are deployed as a single physical deployment unit.</p>\n<p>Here's an interesting quote from Martin Fowler:</p>\n<blockquote>\n<p>You shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile.</p>\n</blockquote>\n<p><em>— <a href=\"https://martinfowler.com/bliki/MonolithFirst.html\">Martin Fowler</a></em></p>\n<p>And I wholeheartedly agree with this. Better yet, consider starting with a modular monolith.</p>\n<p>Even Google is jumping on board the modular monolith trend in their recent research paper, <a href=\"https://dl.acm.org/doi/pdf/10.1145/3593856.3595909\">Towards Modern Development of Cloud Applications</a>.</p>\n<p>Here are the five main challenges Google identified with microservices:</p>\n<ul>\n<li><strong>Performance</strong> - The overhead of serializing data and sending it across the network has a noticeable impact on performance.</li>\n<li><strong>Correctness</strong> - It's difficult to reason about the correctness of a distributed system when there are many interactions between components.</li>\n<li><strong>Management</strong> - We have to manage multiple different applications, each with its release schedule.</li>\n<li><strong>Frozen APIs</strong> - Once an API is established, it becomes hard to change without breaking any existing API consumers.</li>\n<li><strong>Development speed</strong> - Making a change in one microservice may affect many other microservices, which requires carefully planning deployments.</li>\n</ul>\n<p>When you factor in the complexity of distributed systems, starting with a modular monolith becomes increasingly compelling.\nI also recommend reading about the <a href=\"https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing\">fallacies of distributed computing</a> if you're unfamiliar with them.</p>\n<p>Well-defined, in-process components (modules) can be an excellent stepping stone to out-of-process components (services).</p>\n<h2>Benefits of a Modular Monolith</h2>\n<p>Modular monoliths have many benefits. So, I want to highlight a few that I consider important:</p>\n<ul>\n<li><strong>Simplified deployment</strong> - Unlike microservices, which require complex deployment strategies, a modular monolith can be deployed as a single unit.</li>\n<li><strong>Improved performance</strong> - Communication between modules occurs in-process. This means that there's no network latency or data serialization/deserialization overhead.</li>\n<li><strong>Enhanced development velocity</strong> - There's a single codebase to manage, simplifying debugging and the overall development experience.</li>\n<li><strong>Easier transaction management</strong> - Managing transactions in a distributed system is very challenging. Modular monoliths simplify this since modules can share the same database.</li>\n<li><strong>Lower operational complexity</strong> - Modular monoliths reduce the operational overhead that comes with managing and deploying a distributed microservices system.</li>\n<li><strong>Easier transition to Microservices</strong> - A well-structured modular monolith offers a clear path to a microservices architecture.\nYou can gradually <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\">extract modules into separate services</a> when the need arises.</li>\n</ul>\n<h2>Modular Monolith vs Microservices</h2>\n<p>The biggest difference between <a href=\"https://milanjovanovic.tech/blog/modular-monolith-vs-microservices\"><strong>modular monoliths and microservices</strong></a> is how they're deployed.\nMicroservices elevate the logical boundaries inside a modular monolith into physical boundaries.</p>\n<p>Microservices give you a clear strategy for modularity and decomposing the <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>bounded contexts</strong></a>.\nBut, you can also achieve this without building a distributed system.\nThe problem is people end up using microservices to enforce code boundaries.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_080/modular_monolith_vs_microservices.png\" alt=\"Modular monolith vs. microservices.\">\n<p>Instead, you can build a modular monolith to get most of the same benefits.\nModular monoliths give you high cohesion, low coupling, data encapsulation, focus on business functionalities, and more.</p>\n<p>Microservices give you all that, plus independent deployments, independent scalability, and the ability to use different technology stacks per service.</p>\n<blockquote>\n<p>Choose microservices for the benefits, not because your monolithic codebase is a mess.</p>\n</blockquote>\n<p><em>— <a href=\"https://twitter.com/simonbrown\">Simon Brown</a></em></p>\n<h2>Next Steps</h2>\n<p>Modular monoliths offer a compelling way to structure applications.\nThey balance the benefits of well-organized code, scalability potential, and a smooth path for transitioning to microservices if needed.\nIf you want to improve the maintainability and adaptability of your software, consider exploring modular monoliths.</p>\n<p>Want to dive deeper into modular monoliths? Check out these resources:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\">Modular Monolith Communication Patterns</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/modular-monolith-data-isolation\">Modular Monolith Data Isolation</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\">Monolith to Microservices: How a Modular Monolith Helps</a></li>\n<li><a href=\"https://youtu.be/Xo3rsiZYsJQ\">Modular Monoliths: How To Build One &amp; Lessons Learned</a></li>\n<li><a href=\"https://youtu.be/z3piPJ7x4WU\">How to Structure a Modular Monolith Project in .NET</a></li>\n<li><a href=\"https://youtu.be/5dilYMii9T4\">Getting Started with Modular Monoliths in .NET</a></li>\n<li><a href=\"https://milanjovanovic.tech/modular-monolith-architecture\">Modular Monolith Architecture course</a></li>\n</ul>\n<p>That's all for today. Stay awesome, and I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/what-is-a-modular-monolith",
            "title": "What Is a Modular Monolith?",
            "summary": "Modular monoliths blend the simplicity and robustness of traditional monolithic applications with the flexibility and scalability of microservices.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_080.png",
            "date_modified": "2024-03-09T00:00:00.000Z",
            "date_published": "2024-03-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels",
            "content_html": "<p>An in-memory message bus passes messages between loosely coupled components inside the same process, giving you low latency and non-blocking communication.\nYou build one with <code>System.Threading.Channels</code>: an unbounded channel holds the integration events, an <code>IEventBus</code> writes to it, and a <code>BackgroundService</code> reads and dispatches them to handlers.\nMessages are lost if the process goes down.</p>\n<p>Suppose you're building a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-architecture-dotnet\"><strong>modular monolith</strong></a>, a type of software architecture where different components are organized into loosely coupled modules.\nOr you might need to process data asynchronously.\nYou'll need a tool or service that allows you to implement this.</p>\n<p>Messaging plays a crucial role in modern software architecture, enabling communication and coordination between loosely coupled components.</p>\n<p>An in-memory message bus is particularly useful when high performance and low latency are critical requirements.</p>\n<p>In today's issue, we will:</p>\n<ul>\n<li>Create the required messaging abstractions</li>\n<li>Build an in-memory message bus using channels</li>\n<li>Implement an integration event processor background job</li>\n<li>Demonstrate how to publish and consume messages asynchronously</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>When To Use an In-Memory Message Bus</h2>\n<p>I have to preface this by saying that an in-memory message bus is far from a silver bullet.\nThere are many caveats to using it, as you will soon learn.</p>\n<p>But first, let's start with the pros of using an in-memory message bus:</p>\n<ul>\n<li>Because it works in memory, you have a very low-latency messaging system</li>\n<li>You can implement asynchronous (non-blocking) communication between components</li>\n</ul>\n<p>However, there are a few drawbacks to this approach:</p>\n<ul>\n<li>Potential for losing messages if the application process goes down</li>\n<li>It only works inside of a single process, so it's not useful in distributed systems</li>\n</ul>\n<p>A practical use case for an in-memory message bus is when building a modular monolith.\nYou can implement <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\">communication between modules</a> using integration events.\nWhen you need to extract some modules into a separate service, you can replace the in-memory bus with a distributed one.</p>\n<h2>Defining The Messaging Abstractions</h2>\n<p>We will need a few abstractions to build our simple messaging system.\nFrom the client's perspective, we really only need two things.\nOne abstraction is to publish messages, and another is to define a message handler.</p>\n<p>The <code>IEventBus</code> interface exposes the <code>PublishAsync</code> method.\nThis is what we will use to publish messages.\nThere's also a generic constraint defined that only allows passing in an <code>IIntegrationEvent</code> instance.</p>\n<pre><code class=\"language-csharp\">public interface IEventBus\n{\n    Task PublishAsync&lt;T&gt;(\n        T integrationEvent,\n        CancellationToken cancellationToken = default)\n        where T : class, IIntegrationEvent;\n}\n</code></pre>\n<p>I want to be practical with the <code>IIntegrationEvent</code> abstraction, so I'll use <a href=\"https://github.com/jbogard/MediatR\">MediatR</a> for the pub-sub support.\nThe <code>IIntegrationEvent</code> interface will inherit from <code>INotification</code>.\nThis allows us to easily define <code>IIntegrationEvent</code> handlers using <code>INotificationHandler&lt;T&gt;</code>.\nAlso, the <code>IIntegrationEvent</code> has an identifier, so we can track its execution.</p>\n<p>The abstract <code>IntegrationEvent</code> serves as a base class for concrete implementations.</p>\n<pre><code class=\"language-csharp\">using MediatR;\n\npublic interface IIntegrationEvent : INotification\n{\n    Guid Id { get; init; }\n}\n\npublic abstract record IntegrationEvent(Guid Id) : IIntegrationEvent;\n</code></pre>\n<h2>Simple In-Memory Queue Using Channels</h2>\n<p>The <code>System.Threading.Channels</code> namespace provides data structures for asynchronously passing messages between producers and consumers.\nChannels implement the <a href=\"https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem\">producer/consumer pattern</a>.\nProducers asynchronously produce data, and consumers asynchronously consume that data.\nIt's an essential pattern for building loosely coupled systems.</p>\n<p>One of the primary motivations behind the adoption of <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/channels\">.NET Channels</a>\nis their exceptional performance characteristics.\nUnlike traditional message queues, Channels operate entirely in memory.\nThis has the disadvantage of the potential for message loss if the application crashes.</p>\n<p>The <code>InMemoryMessageQueue</code> creates an unbounded channel using the <code>Channel.CreateUnbounded</code> bounded.\nThis means the channel can have any number of readers and writers.\nIt also exposes a <code>ChannelReader</code> and <code>ChannelWriter</code>, which allow consumers to publish and consume messages.</p>\n<pre><code class=\"language-csharp\">internal sealed class InMemoryMessageQueue\n{\n    private readonly Channel&lt;IIntegrationEvent&gt; _channel =\n        Channel.CreateUnbounded&lt;IIntegrationEvent&gt;();\n\n    public ChannelReader&lt;IIntegrationEvent&gt; Reader =&gt; _channel.Reader;\n\n    public ChannelWriter&lt;IIntegrationEvent&gt; Writer =&gt; _channel.Writer;\n}\n</code></pre>\n<p>You also need to register the <code>InMemoryMessageQueue</code> as a singleton with dependency injection:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSingleton&lt;InMemoryMessageQueue&gt;();\n</code></pre>\n<h2>Implementing The Event Bus</h2>\n<p>The <code>IEventBus</code> implementation is now straightforward with the use of channels.\nThe <code>EventBus</code> class uses the <code>InMemoryMessageQueue</code> to access the <code>ChannelWriter</code> and write an event to the channel.</p>\n<pre><code class=\"language-csharp\">internal sealed class EventBus(InMemoryMessageQueue queue) : IEventBus\n{\n    public async Task PublishAsync&lt;T&gt;(\n        T integrationEvent,\n        CancellationToken cancellationToken = default)\n        where T : class, IIntegrationEvent\n    {\n        await queue.Writer.WriteAsync(integrationEvent, cancellationToken);\n    }\n}\n</code></pre>\n<p>We will register the <code>EventBus</code> as a singleton service with dependency injection because it's stateless:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSingleton&lt;IEventBus, EventBus&gt;();\n</code></pre>\n<h2>Consuming Integration Events</h2>\n<p>With the <code>EventBus</code> implementing the producer, we need a way to consume the published <code>IIntegrationEvent</code>.\nWe can implement a simple <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\">background service</a> using the built-in <code>IHostedService</code> abstraction.</p>\n<p>The <code>IntegrationEventProcessorJob</code> depends on the <code>InMemoryMessageQueue</code>, but this time for reading (consuming) messages.\nWe'll use the <code>ChannelReader.ReadAllAsync</code> method to get back an <code>IAsyncEnumerable</code>.\nThis allows us to consume all the messages in the <code>Channel</code> asynchronously.</p>\n<p>The <code>IPublisher</code> from MediatR helps us connect the <code>IIntegrationEvent</code> with the respective handlers.\nIt's important to resolve it from a <a href=\"https://milanjovanovic.tech/blog/using-scoped-services-from-singletons-in-aspnetcore\"><strong>custom scope</strong></a> if you want to inject scoped services into the event handlers.</p>\n<pre><code class=\"language-csharp\">internal sealed class IntegrationEventProcessorJob(\n    InMemoryMessageQueue queue,\n    IServiceScopeFactory serviceScopeFactory,\n    ILogger&lt;IntegrationEventProcessorJob&gt; logger)\n    : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        await foreach (IIntegrationEvent integrationEvent in\n            queue.Reader.ReadAllAsync(stoppingToken))\n        {\n            try\n            {\n                using IServiceScope scope = serviceScopeFactory.CreateScope();\n\n                IPublisher publisher = scope.ServiceProvider\n                    .GetRequiredService&lt;IPublisher&gt;();\n\n                await publisher.Publish(integrationEvent, stoppingToken);\n            }\n            catch (Exception ex)\n            {\n                logger.LogError(\n                    ex,\n                    &quot;Something went wrong! {IntegrationEventId}&quot;,\n                    integrationEvent.Id);\n            }\n        }\n    }\n}\n</code></pre>\n<p>Don't forget to register the hosted service:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHostedService&lt;IntegrationEventProcessorJob&gt;();\n</code></pre>\n<h2>Using The In-Memory Message Bus</h2>\n<p>With all of the necessary abstractions in place, we can finally use the in-memory message bus.</p>\n<p>The <code>IEventBus</code> service will write the message to the <code>Channel</code> and immediately return.\nThis allows you to publish messages in a non-blocking way, which can improve performance.</p>\n<pre><code class=\"language-csharp\">internal sealed class RegisterUserCommandHandler(\n    IUserRepository userRepository,\n    IEventBus eventBus)\n    : ICommandHandler&lt;RegisterUserCommand&gt;\n{\n    public async Task&lt;User&gt; Handle(\n        RegisterUserCommand command,\n        CancellationToken cancellationToken)\n    {\n        // First, register the user.\n        User user = CreateFromCommand(command);\n\n        userRepository.Insert(user);\n\n        // Now we can publish the event.\n        await eventBus.PublishAsync(\n            new UserRegisteredIntegrationEvent(user.Id),\n            cancellationToken);\n\n        return user;\n    }\n}\n</code></pre>\n<p>This solves the producer side, but we also need to create a consumer for the <code>UserRegisteredIntegrationEvent</code> message.\nThis part is greatly simplified because I'm using MediatR in this implementation.</p>\n<p>We need to define an <code>INotificationHandler</code> implementation handling the integration event <code>UserRegisteredIntegrationEvent</code>.\nThis will be the <code>UserRegisteredIntegrationEventHandler</code>.</p>\n<p>When the background job reads the <code>UserRegisteredIntegrationEvent</code> from the <code>Channel</code>, it will publish the message and execute the handler.</p>\n<pre><code class=\"language-csharp\">internal sealed class UserRegisteredIntegrationEventHandler\n    : INotificationHandler&lt;UserRegisteredIntegrationEvent&gt;\n{\n    public async Task Handle(\n        UserRegisteredIntegrationEvent event,\n        CancellationToken cancellationToken)\n    {\n        // Asynchronously handle the event.\n    }\n}\n</code></pre>\n<h2>Improvement Points</h2>\n<p>While our basic in-memory message bus is functional, there are several areas we can improve:</p>\n<ul>\n<li><strong>Resilience</strong> - We can introduce retries when we run into exceptions, which will improve the reliability of the message bus.</li>\n<li><strong>Idempotency</strong> - Ask yourself if you want to handle the same message twice.\nThe <a href=\"https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages\">idempotent consumer pattern</a> elegantly solves this problem.</li>\n<li><strong>Dead Letter Queue</strong> - Sometimes, we won't be able to handle a message correctly.\nIt's a good idea to introduce a persistent storage for these messages.\nThis is called a <a href=\"https://aws.amazon.com/what-is/dead-letter-queue/\">Dead Letter Queue</a>, and it allows for troubleshooting at a later time.</li>\n</ul>\n<p>We've covered the key aspects of building an in-memory message bus using .NET Channels.\nYou can extend this further by implementing the improvements for a more robust solution.</p>\n<p>Remember that this implementation only works inside of one process.\nConsider using a real message broker if you need a more reliable solution.</p>\n<p>That's all for today. I'll see you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/lightweight-in-memory-message-bus-using-dotnet-channels",
            "title": "Lightweight In-Memory Message Bus Using .NET Channels",
            "summary": "Suppose you're building a modular monolith, a type of software architecture where different components are organized into loosely coupled modules.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_079.png",
            "date_modified": "2024-03-02T00:00:00.000Z",
            "date_published": "2024-03-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore",
            "content_html": "<p>Define an <code>IEndpoint</code> interface with a single <code>MapEndpoint</code> method and implement it once per endpoint.\nScan the assembly with reflection, register every implementation with dependency injection, then call <code>MapEndpoints</code> at startup to map them all.\nYou can pass in a <code>RouteGroupBuilder</code> to apply a route prefix or API versioning to every endpoint.</p>\n<p>In ASP.NET Core applications using <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-8.0\">Minimal APIs</a>,\nregistering each API endpoint with <code>app.MapGet</code>, <code>app.MapPost</code>, etc., can introduce repetitive code.\nAs projects grow, this manual process becomes increasingly time-consuming and prone to maintenance headaches.</p>\n<p>You can try grouping the <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API endpoints</strong></a> using extension methods so as not to clutter the <code>Program</code> file.\nThis approach scales well as the project grows.\nHowever, it feels like reinventing controllers.</p>\n<p>I like to view each Minimal API endpoint as a <a href=\"https://milanjovanovic.tech/blog/repr-pattern-aspnetcore\"><strong>standalone component</strong></a>.</p>\n<p>The vision I have in my mind aligns nicely with the concept of <a href=\"https://milanjovanovic.tech/blog/vertical-slice-architecture\">vertical slices.</a></p>\n<p>Today, I'll show you how to register your Minimal APIs automatically with a simple abstraction.</p>\n<h2>The Endpoint Comes First</h2>\n<p>Automatically registering Minimal APIs significantly reduces boilerplate, streamlining development.\nIt makes your codebase more concise and improves maintainability by establishing a centralized registration mechanism.</p>\n<p>Let's create a simple <code>IEndpoint</code> abstraction to represent a single endpoint.</p>\n<p>The <code>MapEndpoint</code> accepts an <code>IEndpointRouteBuilder</code>, which we can use to call <code>MapGet</code>, <code>MapPost</code>, etc.</p>\n<pre><code class=\"language-csharp\">public interface IEndpoint\n{\n    void MapEndpoint(IEndpointRouteBuilder app);\n}\n</code></pre>\n<p>Each <code>IEndpoint</code> implementation should contain exactly one Minimal API endpoint definition.</p>\n<p>Nothing prevents you from registering multiple endpoints in the <code>MapEndpoint</code> method.\nBut you (really) shouldn't.</p>\n<p>Additionally, you could implement a code analyzer or <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\">architecture test</a> to enforce this rule.</p>\n<pre><code class=\"language-csharp\">public class GetFollowerStats : IEndpoint\n{\n    public void MapEndpoint(IEndpointRouteBuilder app)\n    {\n        app.MapGet(&quot;users/{userId}/followers/stats&quot;, async (\n            Guid userId,\n            ISender sender) =&gt;\n        {\n            var query = new GetFollowerStatsQuery(userId);\n\n            Result&lt;FollowerStatsResponse&gt; result = await sender.Send(query);\n\n            return result.Match(Results.Ok, CustomResults.Problem);\n        })\n        .WithTags(Tags.Users);\n    }\n}\n</code></pre>\n<h2>Sprinkle Some Reflection Magic</h2>\n<p>Reflection allows us to dynamically examine code at runtime.\nFor Minimal API registration, we'll use reflection to scan our .NET assemblies and find classes that implement <code>IEndpoint</code>.\nThen, we will configure them as services with dependency injection.</p>\n<p>The <code>Assembly</code> parameter should be the assembly that contains the <code>IEndpoint</code> implementations.\nIf you want to have endpoints in multiple assemblies (projects), you can easily extend this method to accept a collection.</p>\n<pre><code class=\"language-csharp\">public static IServiceCollection AddEndpoints(\n    this IServiceCollection services,\n    Assembly assembly)\n{\n    ServiceDescriptor[] serviceDescriptors = assembly\n        .DefinedTypes\n        .Where(type =&gt; type is { IsAbstract: false, IsInterface: false } &amp;&amp;\n                       type.IsAssignableTo(typeof(IEndpoint)))\n        .Select(type =&gt; ServiceDescriptor.Transient(typeof(IEndpoint), type))\n        .ToArray();\n\n    services.TryAddEnumerable(serviceDescriptors);\n\n    return services;\n}\n</code></pre>\n<p>We only need to call this method once from the <code>Program</code> file:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddEndpoints(typeof(Program).Assembly);\n</code></pre>\n<h2>Registering Minimal APIs</h2>\n<p>The final step in our implementation is to register the endpoints automatically.\nWe can create an extension method on the <code>WebApplication</code>, which lets us resolve services using the <code>IServiceProvider</code>.</p>\n<p>We're looking for all registrations of the <code>IEndpoint</code> service.\nThese will be the endpoint classes we can now register with the application by calling <code>MapEndpoint</code>.</p>\n<p>I'm also adding an option to pass in a <code>RouteGroupBuilder</code> if you want to apply conventions to all endpoints.\nA great example is adding a route prefix, authentication, or <a href=\"https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore\">API versioning.</a></p>\n<pre><code class=\"language-csharp\">public static IApplicationBuilder MapEndpoints(\n    this WebApplication app,\n    RouteGroupBuilder? routeGroupBuilder = null)\n{\n    IEnumerable&lt;IEndpoint&gt; endpoints = app.Services\n        .GetRequiredService&lt;IEnumerable&lt;IEndpoint&gt;&gt;();\n\n    IEndpointRouteBuilder builder =\n        routeGroupBuilder is null ? app : routeGroupBuilder;\n\n    foreach (IEndpoint endpoint in endpoints)\n    {\n        endpoint.MapEndpoint(builder);\n    }\n\n    return app;\n}\n</code></pre>\n<h2>Putting It All Together</h2>\n<p>Here's what the <code>Program</code> file could look like when we put it all together.</p>\n<p>We're calling <code>AddEndpoints</code> to register the <code>IEndpoint</code> implementations.</p>\n<p>Then, we're calling <code>MapEndpoints</code> to automatically register the Minimal APIs.</p>\n<p>I'm also configuring a route prefix and API Versioning for each endpoint using a <code>RouteGroupBuilder</code>.</p>\n<pre><code class=\"language-csharp\">WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\n\nbuilder.Services.AddEndpoints(typeof(Program).Assembly);\n\nWebApplication app = builder.Build();\n\nApiVersionSet apiVersionSet = app.NewApiVersionSet()\n    .HasApiVersion(new ApiVersion(1))\n    .ReportApiVersions()\n    .Build();\n\nRouteGroupBuilder versionedGroup = app\n    .MapGroup(&quot;api/v{version:apiVersion}&quot;)\n    .WithApiVersionSet(apiVersionSet);\n\napp.MapEndpoints(versionedGroup);\n\napp.Run();\n</code></pre>\n<h2>Takeaway</h2>\n<p>Automatic Minimal API registration with techniques like reflection can significantly improve developer efficiency and project maintainability.</p>\n<p>While highly beneficial, it's important to acknowledge the potential <strong>performance impact of reflection</strong> on application startup.</p>\n<p>So, an improvement point could be using source generators for pre-compiled registration logic.</p>\n<p>A few alternatives worth exploring:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-structure-minimal-apis\">Extension methods</a></li>\n<li><a href=\"https://fast-endpoints.com/\">FastEndpoints</a></li>\n<li><a href=\"https://github.com/CarterCommunity/Carter\">Carter</a></li>\n</ul>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<p><strong>P.S.</strong> Here's the complete <a href=\"https://github.com/m-jovanovic/minimal-endpoints\">source code</a> for this article.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore",
            "title": "Automatically Register Minimal APIs in ASP.NET Core",
            "summary": "In ASP.NET Core applications using Minimal APIs, registering each API endpoint with app.MapGet, app.MapPost, etc. can introduce repetitive code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_078.png",
            "date_modified": "2024-02-24T00:00:00.000Z",
            "date_published": "2024-02-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/using-scoped-services-from-singletons-in-aspnetcore",
            "content_html": "<p>ASP.NET Core refuses to inject a scoped service into a singleton, so you inject <code>IServiceScopeFactory</code> instead, create a scope, and resolve the scoped service from that scope.\nThis is how you use an EF Core <code>DbContext</code> inside a background service.\nIn middleware, inject the scoped service as an <code>InvokeAsync</code> parameter so it shares the current request's scope.</p>\n<p>Did you ever need to inject a scoped service into a singleton service?</p>\n<p>I often need to resolve a scoped service, like the EF Core <code>DbContext</code>, in a background service.</p>\n<p>Another example is when you need to resolve a scoped service in ASP.NET Core middleware.</p>\n<p>If you ever tried this, you were probably greeted with an exception similar to this one:</p>\n<pre><code>System.InvalidOperationException: Cannot consume scoped service 'Scoped' from singleton 'Singleton'.\n</code></pre>\n<p>Today, I'll explain how you can solve this problem and safely use scoped services from within singletons in ASP.NET Core.</p>\n<h2>ASP.NET Core Service Lifetimes</h2>\n<p>ASP.NET Core has three <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes\">service lifetimes</a>:</p>\n<ul>\n<li>Transient</li>\n<li>Singleton</li>\n<li>Scoped</li>\n</ul>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#transient\">Transient services</a> are created each time they're requested from the service container.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped\">Scoped services</a> are created once within the scope's lifetime.\nFor ASP.NET Core applications, a new scope is created for each request.\nThis is how you can resolve scoped services within a given request.</p>\n<p>ASP.NET Core applications also have a root <code>IServiceProvider</code> used to resolve <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#singleton\">singleton services.</a></p>\n<p>So, what can we do if resolving a scoped service from a singleton throws an exception?</p>\n<h2>The Solution - <code>IServiceScopeFactory</code></h2>\n<p>What if you want to resolve a scoped service inside a <a href=\"https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core\">background service</a>?</p>\n<p>You can create a new scope (<code>IServiceScope</code>) with its own <code>IServiceProvider</code> instance.\nThe scoped <code>IServiceProvider</code> can be used to resolve scoped services.\nWhen the scope is disposed, all disposable services created within that scope are also disposed.</p>\n<p>Here's an example of using the <code>IServiceScopeFactory</code> to create a new <code>IServiceScope</code>.\nWe're using the scope to resolve the <code>ApplicationDbContext</code>, which is a scoped service.</p>\n<p>The <code>BackgroundJob</code> is registered as a singleton when calling <code>AddHostedService&lt;BackgroundJob&gt;</code>.</p>\n<pre><code class=\"language-csharp\">public class BackgroundJob(IServiceScopeFactory serviceScopeFactory)\n    : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        using IServiceScope scope = serviceScopeFactory.CreateScope();\n\n        var dbContext = scope\n            .ServiceProvider\n            .GetRequiredService&lt;ApplicationDbContext&gt;();\n\n        // Do some background processing with the EF database context.\n        await DoWorkAsync(dbContext);\n    }\n}\n</code></pre>\n<h2>Scoped Services in Middleware</h2>\n<p>What if you want to use a scoped service in <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\">ASP.NET Core middleware</a>?</p>\n<p>Middleware is constructed once per application lifetime.</p>\n<p>If you try injecting a scoped service, you'll get an exception:</p>\n<pre><code>System.InvalidOperationException: Cannot resolve scoped service 'Scoped' from root provider.\n</code></pre>\n<p>There are two ways to get around this.</p>\n<p>First, you could use the previous approach with creating a new scope using <code>IServiceScopeFactory</code>.\nYou'll be able to resolve scoped services.\nBut, they won't share the same lifetime as the other scoped service in the same request.\nThis could even be a problem depending on your requirements.</p>\n<p>Is there a better way?</p>\n<p>Middleware allows you to inject scoped services in the <code>InvokeAsync</code> method.\nThe injected services will use the current request's scope, so they'll have the same lifetime as any other scoped service.</p>\n<pre><code class=\"language-csharp\">public class ConventionalMiddleware(RequestDelegate next)\n{\n    public async Task InvokeAsync(\n        HttpContext httpContext,\n        IMyScopedService scoped)\n    {\n        scoped.DoSomething();\n\n        await _next(httpContext);\n    }\n}\n</code></pre>\n<h2><code>IServiceScopeFactory</code> vs. <code>IServiceProvider</code></h2>\n<p>You might see examples using the <code>IServiceProvider</code> to create a scope instead of the <code>IServiceScopeFactory</code>.</p>\n<p>What's the difference between these two approaches?</p>\n<p>The <a href=\"https://github.com/aspnet/DependencyInjection/blob/94b9cc9ace032f838e068702cc70ce57cc883bc7/src/DI.Abstractions/ServiceProviderServiceExtensions.cs#L125\"><code>CreateScope</code> method from <code>IServiceProvider</code></a>\nresolves an <code>IServiceScopeFactory</code> instance and calls <code>CreateScope()</code> on it:</p>\n<pre><code class=\"language-csharp\">public static IServiceScope CreateScope(this IServiceProvider provider)\n{\n    return provider.GetRequiredService&lt;IServiceScopeFactory&gt;().CreateScope();\n}\n</code></pre>\n<p>So, if you want to use the <code>IServiceProvider</code> directly to create a scope, that's fine.</p>\n<p>However, the <code>IServiceScopeFactory</code> is a more direct way to achieve the desired result.</p>\n<h2>Summary</h2>\n<p>Understanding the difference between Transient, Scoped, and Singleton lifetimes is crucial for managing dependencies in ASP.NET Core applications.</p>\n<p>The <code>IServiceScopeFactory</code> provides a solution when you need to resolve scoped services from singletons.\nIt allows you to create a new scope, which you can use to resolve scoped services.</p>\n<p>In middleware, we can inject scoped services into the <code>InvokeAsync</code> method.\nThis also ensures the services use the current request's scope and lifecycle.</p>\n<p>Thanks for reading, and I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/using-scoped-services-from-singletons-in-aspnetcore",
            "title": "Using Scoped Services From Singletons in ASP.NET Core",
            "summary": "Did you ever need to inject a scoped service into a singleton service? I'll explain how you can solve this problem and safely use scoped services from within…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_077.png",
            "date_modified": "2024-02-17T00:00:00.000Z",
            "date_published": "2024-02-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/getting-the-current-user-in-clean-architecture",
            "content_html": "<p>Define an <code>IUserContext</code> abstraction in the Application layer that exposes what your use cases need, such as the current <code>UserId</code>.\nImplement it in the Infrastructure layer with <code>IHttpContextAccessor</code> and the claims on the <code>ClaimsPrincipal</code>.\nYour use cases stay decoupled from identity, so the Clean Architecture dependency rule holds.</p>\n<p>The applications you build serve your users (customers) to help them solve some problems.\nIt's a common requirement that you will need to know who the current application user is.</p>\n<p>How do you get the current user's information in a Clean Architecture use case?</p>\n<p>Use cases live in the <a href=\"https://milanjovanovic.tech/blog/application-layer-clean-architecture\"><strong>Application layer</strong></a>, where you can't introduce external concerns.\nOtherwise, you will be breaking the dependency rule.</p>\n<p>Let's say you want to know who the current user is to determine if they can access some resource.\nThis is your typical <a href=\"https://milanjovanovic.tech/blog/authentication-authorization-clean-architecture\"><strong>resource-based authorization</strong></a> check.\nBut you have to interact with the identity provider to get this information.\nThis breaks the <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\">dependency rule in Clean Architecture.</a></p>\n<p>I've seen this problem confuse developers who are new to Clean Architecture.</p>\n<p>In today's issue, I'll show you how to access the current user's information in a clean way.</p>\n<h2>Start With an Abstraction</h2>\n<p>The inner layers in Clean Architecture define abstractions for external concerns.\nFrom the Application layer's perspective, authentication and user identity are external concerns.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/infrastructure-layer-clean-architecture\"><strong>Infrastructure layer</strong></a> deals with external concerns, including authentication and identity management.\nThis is where you would implement the abstraction.</p>\n<p>My preferred approach is creating an <code>IUserContext</code> abstraction.\nThe main information I need is the <code>UserId</code> of the current user.\nBut you can expand the <code>IUserContext</code> with any other data you think is necessary.</p>\n<pre><code class=\"language-csharp\">public interface IUserContext\n{\n    bool IsAuthenticated { get; }\n\n    Guid UserId { get; }\n}\n</code></pre>\n<p>Let's see how to implement the <code>IUserContext</code>.</p>\n<h2>Implementing the UserContext</h2>\n<p>The <code>UserContext</code> class is the <code>IUserContext</code> implementation in the Infrastructure layer.\nWe need to inject the <code>IHttpContextAccessor</code>, which allows us to access the <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimsprincipal?view=net-8.0\"><code>ClaimsPrincipal</code></a>\nthrough the <code>User</code> property.\nThe <code>ClaimsPrincipal</code> gives you access to the current user's claims, containing the required information.</p>\n<p>In this example, I'm throwing an exception if any of the properties evaluate to <code>null</code>.\nYou can decide if throwing an exception makes sense for you.</p>\n<p>I also want to share an important remark here about <code>IHttpContextAccessor</code>.\nWe're using it to access the <code>HttpContext</code> instance — <strong>which only exists during an API request</strong>.\nOutside an API request, the <code>HttpContext</code> will be null, and the <code>UserContext</code> will throw an exception when accessing its properties.</p>\n<pre><code class=\"language-csharp\">internal sealed class UserContext(IHttpContextAccessor httpContextAccessor)\n    : IUserContext\n{\n    public Guid UserId =&gt;\n        httpContextAccessor\n            .HttpContext?\n            .User\n            .GetUserId() ??\n        throw new ApplicationException(&quot;User context is unavailable&quot;);\n\n    public bool IsAuthenticated =&gt;\n        httpContextAccessor\n            .HttpContext?\n            .User\n            .Identity?\n            .IsAuthenticated ??\n        throw new ApplicationException(&quot;User context is unavailable&quot;);\n}\n</code></pre>\n<p>Here's the <code>GetUserId</code> extension method that's used in the <code>UserContext.UserId</code> property.\nIt's looking for a claim with the <code>ClaimTypes.NameIdentifier</code> name, and parsing that value into a <code>Guid</code>.\nYou can replace this with a different type to match the user identity in your system.</p>\n<pre><code class=\"language-csharp\">internal static class ClaimsPrincipalExtensions\n{\n    public static Guid GetUserId(this ClaimsPrincipal? principal)\n    {\n        string? userId = principal?.FindFirstValue(ClaimTypes.NameIdentifier);\n\n        return Guid.TryParse(userId, out Guid parsedUserId) ?\n            parsedUserId :\n            throw new ApplicationException(&quot;User id is unavailable&quot;);\n    }\n}\n</code></pre>\n<h2>Using The Current User Information</h2>\n<p>Now that you have the <code>IUserContext</code>, you can use it from the Application layer.</p>\n<p>A common requirement is checking if the current user can access some resources.</p>\n<p>Here's an example using the <code>GetInvoiceQueryHandler</code>, which queries the database for an invoice.\nAfter projecting the result to an <code>InvoiceResponse</code> object, we check if the current user is the one to whom the invoice was issued.\nYou can also apply this check as part of the database query.\nBut performing it in memory lets you return a different response to the user when they aren't authorized.\nFor example, a <a href=\"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.3\">403 Forbidden</a> might be appropriate.</p>\n<pre><code class=\"language-csharp\">class GetInvoiceQueryHandler(IAppDbContext dbContext, IUserContext userContext)\n    : IQueryHandler&lt;GetInvoiceQuery, InvoiceResponse&gt;\n{\n    public async Task&lt;Result&lt;InvoiceResponse&gt;&gt; Handle(\n        GetInvoiceQuery request,\n        CancellationToken cancellationToken)\n    {\n        InvoiceResponse? invoiceResponse = await dbContext\n            .Invoices\n            .ProjectTo&lt;InvoiceResponse&gt;()\n            .FirstOrDefaultAsync(\n                invoice =&gt; invoice.Id == request.InvoiceId,\n                cancellationToken);\n\n        if (invoiceResponse is null ||\n            invoiceResponse.IssuedToUserId != userContext.UserId)\n        {\n            return Result.Failure&lt;InvoiceResponse&gt;(InvoiceErrors.NotFound);\n        }\n\n        return invoiceResponse;\n    }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Incorporating user identity and authentication into <a href=\"https://milanjovanovic.tech/blog/why-clean-architecture-is-great-for-complex-projects\">Clean Architecture</a>\ndoesn't have to compromise the integrity of your design.\nThe Application layer should remain decoupled from external concerns such as identity management.</p>\n<p>We respect the <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\">Clean Architecture dependency rule</a> by\nabstracting user-related information through the <code>IUserContext</code> interface and implementing it within the Infrastructure layer.</p>\n<p>With this strategy, you can effectively manage user information, support authorization checks, and ensure your application remains robust and adaptable to future changes.</p>\n<p>Remember, the key is in defining clear abstractions and respecting the architecture's boundaries.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/getting-the-current-user-in-clean-architecture",
            "title": "Getting the Current User in Clean Architecture",
            "summary": "The applications you build serve your users (customers), to help them solve some problems.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_076.png",
            "date_modified": "2024-02-10T00:00:00.000Z",
            "date_published": "2024-02-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-i-made-my-efcore-query-faster-with-batching",
            "content_html": "<p>Querying the database inside a <code>foreach</code> loop costs one round trip per item.\nBatching removes that: collect the ids up front, fetch every related row in a single query, and build a dictionary for in-memory lookup.\nIn this issue's benchmark, that took the endpoint from 1913.3 us to 558.6 us, 3.42x faster against a local SQL database.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/\">EF Core</a> is a fantastic ORM if you're building .NET applications.</p>\n<p>But it's a tool like any other. And you can end up using it in a suboptimal way.</p>\n<p>Today, I'll show you a simple idea I used to get an almost <strong>4x performance improvement</strong>.</p>\n<p>I'm not saying you'll see the same result, but understanding the idea will make your queries faster.</p>\n<h2>Why This Query is Suboptimal</h2>\n<p>Here's the example I want to use to explain this powerful idea.\nIt's taken from a production app I was working on, but I simplified it for this example.</p>\n<p>We're using an <code>InvoiceService</code> to get a collection of invoices for a given company.\nThe invoices could come from a third-party API or some other persistence store.\nWe're lacking detailed line item information, so we're querying the database to fill in the missing data.</p>\n<p>The highlighted <a href=\"https://learn.microsoft.com/en-us/ef/core/querying/\">LINQ query</a> below isn't bad by itself.\nIt returns all the line items in one database query (round trip).</p>\n<p>But it's missing one important realization that can unlock further performance gains.</p>\n<p>Because we're iterating over the invoices, we're <strong>querying the database many times</strong>.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;invoices/{companyId}&quot;, (\n    long companyId,\n    InvoiceService invoiceService,\n    AppDbContext dbContext) =&gt;\n{\n    IEnumerable&lt;Invoice&gt; invoices = invoiceService.GetForCompanyId(\n        companyId,\n        take: 10);\n\n    var invoiceDtos = new List&lt;InvoiceDto&gt;();\n    foreach (var invoice in invoices)\n    {\n        var invoiceDto = new InvoiceDto\n        {\n            Id = invoice.Id,\n            CompanyId = invoice.CompanyId,\n            IssuedDate = invoice.IssuedDate,\n            DueDate = invoice.DueDate,\n            Number = invoice.Number\n        };\n\n        var lineItemDtos = await dbContext\n            .LineItems\n            .Where(li =&gt; invoice.LineItemIds.Contains(li.Id))\n            .Select(li =&gt; new LineItemDto\n            {\n                Id = li.Id,\n                Name = li.Name,\n                Price = li.Price,\n                Quantity = li.Quantity\n            })\n            .ToArrayAsync();\n\n        invoiceDto.LineItems = lineItemDtos;\n\n        invoiceDtos.Add(invoiceDto);\n    }\n\n    return invoiceDtos;\n});\n</code></pre>\n<p>Once you figure this out, the solution comes down to applying a simple idea.</p>\n<p>Instead of fetching the line items for each invoice, we can query all the line items ahead of time.</p>\n<h2>Batching to the Rescue</h2>\n<p>Here's the same query, but refactored to only query the line items once.\nThis means there's just a single round trip to the database.</p>\n<p>There are three components to the final design:</p>\n<ul>\n<li>Querying all the <code>LineItems</code> in a single database round-trip</li>\n<li>Creating a <code>LineItemDto</code> dictionary for fast lookup</li>\n</ul>\n<p>Once we have the dictionary, we can loop through the invoices and assign the line items.\nPopulating a line item becomes a dictionary lookup (cheap) instead of a database query (expensive).</p>\n<p>Before deciding if this solution makes sense, you should consider a few more things.</p>\n<p>How many records can you load from the database at once?</p>\n<p>Each invoice contains ~20 line items on average, and we're only fetching ten invoices.\nSo, we're loading ~200 line items from the database.\nMost applications can handle this load.\nBut things could be different if you're fetching thousands of rows.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;invoices/{companyId}&quot;, (\n    long companyId,\n    InvoiceService invoiceService,\n    AppDbContext dbContext) =&gt;\n{\n    IEnumerable&lt;Invoice&gt; invoices = invoiceService.GetForCompanyId(\n        companyId,\n        take: 10);\n\n    long[] lineItemIds = invoices\n        .SelectMany(invoice =&gt; invoice.LineItemIds)\n        .ToArray();\n\n    var lineItemDtos = await dbContext\n        .LineItems\n        .Where(li =&gt; lineItemIds.Contains(li.Id))\n        .Select(li =&gt; new LineItemDto\n        {\n            Id = li.Id,\n            Name = li.Name,\n            Price = li.Price,\n            Quantity = li.Quantity\n        })\n        .ToListAsync();\n\n    Dictionary&lt;long, LineItemDto&gt; lineItemsDictionary =\n        lineItemDtos.ToDictionary(keySelector: li =&gt; li.Id);\n\n    var invoiceDtos = new List&lt;InvoiceDto&gt;();\n    foreach (var invoice in invoices)\n    {\n        var invoiceDto = new InvoiceDto\n        {\n            Id = invoice.Id,\n            CompanyId = invoice.CompanyId,\n            IssuedDate = invoice.IssuedDate,\n            DueDate = invoice.DueDate,\n            Number = invoice.Number,\n            LineItems = invoice\n                .LineItemIds\n                .Select(li =&gt; lineItemsDictionary[li])\n                .ToArray()\n        };\n\n        invoiceDtos.Add(invoiceDto);\n    }\n\n    return invoiceDtos;\n})\n</code></pre>\n<h2>How Much Faster?</h2>\n<p>It seems plausible that the batch variant would be faster. Right?</p>\n<p>We have N queries (one per invoice) in the first version and a single query in the batched version.</p>\n<p>Here are the benchmark results I got using <a href=\"https://github.com/dotnet/BenchmarkDotNet\">BenchmarkDotNet</a>:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_075/benchmark.png\" alt=\"Benchmark results: batched query averages 558.6 microseconds versus 1,919.3 for foreach queries\">\n<p>The foreach version takes <strong>1913.3 us</strong> (microseconds) on average.<br>\nThe batched version takes <strong>558.6 us</strong> on average.</p>\n<p>That's <strong>3.42x faster</strong> with the batched version. This is with a local SQL database.</p>\n<p>The batched version should be even faster if you're querying a remote database because of the impact of network round-trip time.\nIt quickly adds up when you have N queries (foreach version).</p>\n<h2>Takeaway</h2>\n<p>The power of this approach lies in its simplicity and efficiency.\nBy batching database queries, we significantly reduce the number of round trips to the database.\nThis is often one of the biggest performance bottlenecks.</p>\n<p>But it's crucial to understand that this approach is not a one-size-fits-all solution.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>EF Core</strong></a> offers many features and optimizations, but it's up to the developer to use them effectively.</p>\n<p>Finally, always remember to measure and benchmark.\nThe improvements we saw in this case were quantified through benchmarks.\nWithout proper measurement, it's easy to make changes that inadvertently degrade performance.</p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-i-made-my-efcore-query-faster-with-batching",
            "title": "How I Made My EF Core Query 3.42x Faster With Batching",
            "summary": "EF Core is a fantastic ORM if you're building .NET applications. Today, I'll show you a simple idea I used to get an almost 4x performance improvement.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_075.png",
            "date_modified": "2024-02-03T00:00:00.000Z",
            "date_published": "2024-02-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-build-a-url-shortener-with-dotnet",
            "content_html": "<p>To build a URL shortener in .NET, expose two Minimal API endpoints.\nOne generates a unique code for a long URL and stores the mapping, and the other looks up the code and redirects to the original URL.\nStore the mappings in a database like PostgreSQL, and add a distributed cache like Redis to improve read performance.</p>\n<p>A <strong>URL shortener</strong> is a simple yet powerful tool that converts long URLs into more manageable, shorter versions.\nThis is particularly useful for sharing links on platforms with character limits or improving user experience by reducing clutter.\nTwo popular URL shorteners are <a href=\"https://bitly.com/\">Bitly</a> and <a href=\"https://tinyurl.com/app\">TinyURL</a>.\nDesigning a URL shortener is an interesting challenge with fun problems to solve.</p>\n<p>But how would you build a URL shortener in .NET?</p>\n<p>URL shorteners have two core functionalities:</p>\n<ul>\n<li>Generating a unique code for a given URL</li>\n<li>Redirecting users who access the short link to the original URL</li>\n</ul>\n<p>Today, I'll guide you through the design, implementation, and considerations for creating your URL shortener.</p>\n<h2>URL Shortener System Design</h2>\n<p>Here's the high-level system design for our URL shortener.\nWe want to expose two endpoints.\nOne to shorten a long URL and the other to redirect users based on a shortened URL.\nThe shortened URLs are stored in a <a href=\"https://www.postgresql.org/\">PostgreSQL</a> database in this example.\nWe can introduce a distributed cache like <a href=\"https://redis.io/\">Redis</a> to the system to improve read performance.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_074/url_shortener.png\" alt=\"URL shortener system design. It contains two API endpoints, a PostgreSQL database, and a Redis cache.\">\n<p>We first need to ensure a large number of short URLs.\nWe're going to assign a unique code to each long URL, and use it to generate the shortened URL.\nThe unique code length and set of characters determine how many short URLs the system can generate.\nWe will discuss this in more detail when we implement unique code generation.</p>\n<p>We're going to use the random code generation strategy.\nIt's straightforward to implement and has an acceptably low rate of collisions.\nThe trade-off we're making is increased latency, but we will also explore other options.</p>\n<h2>The Data Model</h2>\n<p>Let's start by figuring out what we will store in the database.\nOur data model is straightforward.\nWe have a <code>ShortenedUrl</code> class representing the URLs stored in our system:</p>\n<pre><code class=\"language-csharp\">public class ShortenedUrl\n{\n    public Guid Id { get; set; }\n\n    public string LongUrl { get; set; } = string.Empty;\n\n    public string ShortUrl { get; set; } = string.Empty;\n\n    public string Code { get; set; } = string.Empty;\n\n    public DateTime CreatedOnUtc { get; set; }\n}\n</code></pre>\n<p>This class includes properties for the original URL (<code>LongUrl</code>), the shortened URL (<code>ShortUrl</code>), and a unique code (<code>Code</code>) that represents the shortened URL.\nThe <code>Id</code> and <code>CreatedOnUtc</code> fields are used for database and tracking purposes.\nThe users will send the unique <code>Code</code> to our system, which will try to find a matching <code>LongUrl</code> and redirect them.</p>\n<p>In addition, we will also define an EF <code>ApplicationDbContext</code> class, which is responsible for configuring our entity and setting up our database context.\nI'm doing two things here to improve performance:</p>\n<ul>\n<li>Configuring the <code>Code</code> maximum length with <code>HasMaxLength</code></li>\n<li>Defining a unique index on the <code>Code</code> column</li>\n</ul>\n<p>A unique index shields us from concurrency conflicts, so we will never have duplicate <code>Code</code> values persisted in the database.\nSetting the maximum length for this column saves storage space, and it's a requirement for indexing string columns in some databases.</p>\n<p>Note that some databases treat strings in a case-insensitive way.\nThis severely reduces the number of available short URLs.\nYou want to configure the database to treat the unique code in a case-sensitive way.</p>\n<pre><code class=\"language-csharp\">public class ApplicationDbContext : DbContext\n{\n    public ApplicationDbContext(DbContextOptions options)\n        : base(options)\n    {\n    }\n\n    public DbSet&lt;ShortenedUrl&gt; ShortenedUrls { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity&lt;ShortenedUrl&gt;(builder =&gt;\n        {\n            builder\n                .Property(shortenedUrl =&gt; shortenedUrl.Code)\n                .HasMaxLength(ShortLinkSettings.Length);\n\n            builder\n                .HasIndex(shortenedUrl =&gt; shortenedUrl.Code)\n                .IsUnique();\n        });\n    }\n}\n</code></pre>\n<h2>Unique Code Generation</h2>\n<p>The most crucial part of our URL shortener is generating a unique code for each URL.\nThere are a few different algorithms you can choose to implement this.\nWe want an even distribution of unique codes across all possible values.\nThis helps to reduce potential collisions.</p>\n<p>I will implement a random, unique code generator with a predefined alphabet.\nIt's simple to implement, and the chance of collision is relatively low.\nStill, there are more performant solutions than this, but more on this later.</p>\n<p>Let's define a <code>ShortLinkSettings</code> class that contains two constants.\nOne is for defining the length of the unqualified code we will generate.\nThe other constant is the alphabet we will use to generate the random code.</p>\n<pre><code class=\"language-csharp\">public static class ShortLinkSettings\n{\n    public const int Length = 7;\n    public const string Alphabet =\n        &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&quot;;\n}\n</code></pre>\n<p>The alphabet has <code>62</code> characters, which gives us <code>62^7</code> possible unique code combinations.</p>\n<p>If you're wondering, this is <code>3,521,614,606,208</code> combinations.</p>\n<p>Spelled out: three trillion, five hundred twenty-one billion, six hundred fourteen million, six hundred six thousand, two hundred eight.</p>\n<p>Those are quite a few unique codes, which will be enough for our URL shortener.</p>\n<p>Now, let's implement our <code>UrlShorteningService</code>, which handles generating unique codes.\nThis service generates a random string of the specified length using our predefined alphabet.\nIt checks against the database to ensure uniqueness.</p>\n<pre><code class=\"language-csharp\">public class UrlShorteningService(ApplicationDbContext dbContext)\n{\n    private readonly Random _random = new();\n\n    public async Task&lt;string&gt; GenerateUniqueCode()\n    {\n        var codeChars = new char[ShortLinkSettings.Length];\n        const int maxValue = ShortLinkSettings.Alphabet.Length;\n\n        while (true)\n        {\n            for (var i = 0; i &lt; ShortLinkSettings.Length; i++)\n            {\n                var randomIndex = _random.Next(maxValue);\n\n                codeChars[i] = ShortLinkSettings.Alphabet[randomIndex];\n            }\n\n            var code = new string(codeChars);\n\n            if (!await dbContext.ShortenedUrls.AnyAsync(s =&gt; s.Code == code))\n            {\n                return code;\n            }\n        }\n    }\n}\n</code></pre>\n<p><strong>Downsides and Improvement Points</strong></p>\n<p>The downside of this implementation is increased latency because we're checking each code we generate against the database.\nAn improvement point could be generating the unique codes in the database ahead of time.</p>\n<p>Another improvement point could be using a fixed number of iterations instead of an infinite loop.\nIn case of multiple collisions in a row, the current implementation would continue until a unique value is found.\nConsider throwing an exception instead after a few collisions in a row.</p>\n<h2>URL Shortening</h2>\n<p>Now that our core business logic is ready, we can expose an endpoint to shorten URLs.\nWe can use a simple <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint.</p>\n<p>This endpoint accepts a URL, validates it, and then uses the <code>UrlShorteningService</code> to create a shortened URL, which is then saved to the database.\nWe return the full shortened URL to the client.</p>\n<pre><code class=\"language-csharp\">public record ShortenUrlRequest(string Url);\n\napp.MapPost(&quot;shorten&quot;, async (\n    ShortenUrlRequest request,\n    UrlShorteningService urlShorteningService,\n    ApplicationDbContext dbContext,\n    HttpContext httpContext) =&gt;\n{\n    if (!Uri.TryCreate(request.Url, UriKind.Absolute, out _))\n    {\n        return Results.BadRequest(&quot;The specified URL is invalid.&quot;);\n    }\n\n    var code = await urlShorteningService.GenerateUniqueCode();\n\n    var request = httpContext.Request;\n\n    var shortenedUrl = new ShortenedUrl\n    {\n        Id = Guid.NewGuid(),\n        LongUrl = request.Url,\n        Code = code,\n        ShortUrl = $&quot;{request.Scheme}://{request.Host}/{code}&quot;,\n        CreatedOnUtc = DateTime.UtcNow\n    };\n\n    dbContext.ShortenedUrls.Add(shortenedUrl);\n\n    await dbContext.SaveChangesAsync();\n\n    return Results.Ok(shortenedUrl.ShortUrl);\n});\n</code></pre>\n<p>There is a minor <a href=\"https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking\">race condition</a> here, as we generate a unique code first and then insert it into the database.\nA concurrent request could generate the same unique code and insert it into the database before we complete our transaction.\nHowever, the chances of this happening are low, so I decided not to handle that case.</p>\n<p>Remember that the unique index in the database is still guarding us against duplicate values.</p>\n<h2>URL Redirection</h2>\n<p>The second use case for a URL shortener is redirection when accessing a shortened URL.</p>\n<p>We will expose another Minimal API endpoint for this feature.\nThe endpoint will accept a unique code, find the respective shortened URL, and redirect the user to the original long URL.\nYou can implement additional validation for the specified code before checking if there's a shortened URL in the database.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;{code}&quot;, async (string code, ApplicationDbContext dbContext) =&gt;\n{\n    var shortenedUrl = await dbContext\n        .ShortenedUrls\n        .SingleOrDefaultAsync(s =&gt; s.Code == code);\n\n    if (shortenedUrl is null)\n    {\n        return Results.NotFound();\n    }\n\n    return Results.Redirect(shortenedUrl.LongUrl);\n});\n</code></pre>\n<p>This endpoint looks up the code in the database and, if found, redirects the user to the original long URL.\nThe response will have a <a href=\"https://datatracker.ietf.org/doc/html/rfc7231#section-6.4.3\">302 (Found) status code</a> per the HTTP standards.</p>\n<h2>URL Shortener Improvement Points</h2>\n<p>While our basic URL shortener is functional, there are several areas we can improve:</p>\n<ul>\n<li><strong>Caching</strong>: Implement caching to reduce database load for frequently accessed URLs.</li>\n<li><strong>Horizontal Scaling</strong>: Design the system to scale horizontally to handle increased load.</li>\n<li><strong>Data Sharding</strong>: Implement data sharding to distribute data across multiple databases.</li>\n<li><strong>Analytics</strong>: Introduce analytics to track URL usage and expose reports to users.</li>\n<li><strong>User Accounts</strong>: Allow users to create accounts to manage their URLs.</li>\n</ul>\n<p>We've covered the key components of building a URL shortener using .NET.\nYou can take this further and implement the improvements points for a more robust solution.</p>\n<p>If you want to see me build this from scratch, here's a <a href=\"https://youtu.be/2UoA_PoEvuA\">video tutorial on YouTube.</a></p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-build-a-url-shortener-with-dotnet",
            "title": "How to Build a URL Shortener With .NET",
            "summary": "A URL shortener is a simple yet powerful tool that converts long URLs into more manageable, shorter versions.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_074.png",
            "date_modified": "2024-01-27T00:00:00.000Z",
            "date_published": "2024-01-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture",
            "content_html": "<p>Cross-cutting concerns are application-level functionalities that affect the entire application and span several layers.\nIn Clean Architecture, they belong in the Infrastructure layer, implemented with ASP.NET Core middleware, decorators, or MediatR pipeline behaviors.\nCentralizing them in one location prevents code duplication and tight coupling between components.</p>\n<p>Cross-cutting concerns are software aspects that affect the entire application.\nThese are your common application-level functionalities that span several layers and tiers.\nCross-cutting concerns should be centralized in one location.\nThis prevents code duplication and tight coupling between components.</p>\n<p>A few examples of cross-cutting concerns are:</p>\n<ul>\n<li>Authentication &amp; Authorization</li>\n<li>Logging and tracing</li>\n<li>Exception handling</li>\n<li>Validation</li>\n<li>Caching</li>\n</ul>\n<p>In today's newsletter, I'll show you how to integrate cross-cutting concerns in Clean Architecture.</p>\n<h2>Cross-Cutting Concerns in Clean Architecture</h2>\n<p>In Clean Architecture, <a href=\"https://en.wikipedia.org/wiki/Cross-cutting_concern\">cross-cutting concerns</a>\nplay an essential role in ensuring the maintainability and scalability of your system.\nIdeally, these concerns should be handled separately from the core business logic.\nThis aligns with Clean Architecture's principles, emphasizing the decoupling of concerns and modularity.\nYour core business rules remain uncluttered, and the architecture stays clean and adaptable.</p>\n<p>Ideally, you want to implement cross-cutting concerns in the Infrastructure layer.\nYou can use ASP.NET Core middleware, <a href=\"https://milanjovanovic.tech/blog/decorator-pattern-in-asp-net-core\">decorators</a>, or <a href=\"https://milanjovanovic.tech/blog/mediatr-pipeline-behaviors\"><strong>MediatR pipeline behaviors</strong></a>.\nWhichever approach you decide to use, the guiding idea remains the same.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_073/clean_architecture_cross_cutting_concerns.png\" alt=\"Cross-cutting concerns in Clean Architecture\">\n<p>Let's see how to implement logging, validation, and caching as cross-cutting concerns.</p>\n<h2>Cross-Cutting Concern #1 - Logging</h2>\n<p>Logging is a fundamental aspect of software development, allowing you to look into an application's behavior.\nIt's vital for debugging, monitoring application health, and tracking user activities and system anomalies.\nIn the context of Clean Architecture, logging must be implemented in a way that maintains the separation of concerns.</p>\n<p>An elegant way to achieve this is with <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\">MediatR's</a> <code>IPipelineBehavior</code>.\nBy encapsulating the logging logic inside a pipeline behavior, we ensure that logging is treated as a distinct concern, separate from business logic.\nThis approach enables us to capture detailed information about requests flowing through the application.</p>\n<p>Effective logging should be consistent, context-rich, and non-intrusive.\nUsing <a href=\"https://milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging\">Serilog's</a> structured logging\ncapabilities, we can create logs that are not only informative but also easily queryable.\nThis is essential for understanding the state of the application at any given moment.</p>\n<p>When done correctly, <a href=\"https://milanjovanovic.tech/blog/structured-logging-in-asp-net-core-with-serilog\">structured logging</a>\nprovides invaluable insights into your application without cluttering the core logic.\nIt's a balance of granularity and clarity, ensuring that your logs are a helpful tool rather than a source of noise.</p>\n<pre><code class=\"language-csharp\">using Serilog.Context;\n\ninternal sealed class RequestLoggingPipelineBehavior&lt;TRequest, TResponse&gt;(\n    ILogger&lt;RequestLoggingPipelineBehavior&lt;TRequest, TResponse&gt;&gt; logger)\n    : IPipelineBehavior&lt;TRequest, TResponse&gt;\n    where TRequest : class\n    where TResponse : Result\n{\n    public async Task&lt;TResponse&gt; Handle(\n        TRequest request,\n        RequestHandlerDelegate&lt;TResponse&gt; next,\n        CancellationToken cancellationToken)\n    {\n        string requestName = typeof(TRequest).Name;\n\n        logger.LogInformation(\n            &quot;Processing request {RequestName}&quot;,\n            requestName);\n\n        TResponse result = await next();\n\n        if (result.IsSuccess)\n        {\n            logger.LogInformation(\n                &quot;Completed request {RequestName}&quot;,\n                requestName);\n        }\n        else\n        {\n            using (LogContext.PushProperty(&quot;Error&quot;, result.Error, true))\n            {\n                logger.LogError(\n                    &quot;Completed request {RequestName} with error&quot;,\n                    requestName);\n            }\n        }\n\n        return result;\n    }\n}\n</code></pre>\n<h2>Cross-Cutting Concern #2 - Validation</h2>\n<p>Validation is a critical cross-cutting concern in software engineering.\nIt serves as the first line of defense against incorrect data entering your system.\nValidation guards the application against inconsistent data states and potential security vulnerabilities.</p>\n<p>In the example below, I'm creating a <a href=\"https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation\">validation pipeline behavior</a>.\nThis setup allows for a clean separation of validation logic from business logic.\nThe pipeline behavior ensures that each request is validated before it reaches the core processing logic.</p>\n<p>In approaching validation, it's crucial to distinguish between two types:</p>\n<ul>\n<li>Input validation</li>\n<li>Business rule validation</li>\n</ul>\n<p><strong>Input validation</strong> checks for the correctness and format of the data (like string length, number ranges, and date formats), ensuring it meets the basic criteria before processing.</p>\n<p>On the other hand, business rule validation is more about ensuring that the data adheres to your domain's specific rules and logic.</p>\n<p>Effective validation practices significantly contribute to the resilience and reliability of an application.\nBy enforcing validation rules, you can maintain a high data quality standard and ensure a better user experience.</p>\n<pre><code class=\"language-csharp\">internal sealed class ValidationPipelineBehavior&lt;TRequest, TResponse&gt;(\n    IEnumerable&lt;IValidator&lt;TRequest&gt;&gt; validators)\n    : IPipelineBehavior&lt;TRequest, TResponse&gt;\n    where TRequest : class\n{\n    public async Task&lt;TResponse&gt; Handle(\n        TRequest request,\n        RequestHandlerDelegate&lt;TResponse&gt; next,\n        CancellationToken cancellationToken)\n    {\n        ValidationFailure[] validationFailures = await ValidateAsync(request);\n\n        if (validationFailures.Length != 0)\n        {\n            throw new ValidationException(validationFailures);\n        }\n\n        return await next();\n    }\n\n    private async Task&lt;ValidationFailure[]&gt; ValidateAsync(TRequest request)\n    {\n        if (!validators.Any())\n        {\n            return [];\n        }\n\n        var context = new ValidationContext&lt;TRequest&gt;(request);\n\n        ValidationResult[] validationResults = await Task.WhenAll(\n            validators.Select(validator =&gt; validator.ValidateAsync(context)));\n\n        ValidationFailure[] validationFailures = validationResults\n            .Where(validationResult =&gt; !validationResult.IsValid)\n            .SelectMany(validationResult =&gt; validationResult.Errors)\n            .ToArray();\n\n        return validationFailures;\n    }\n}\n</code></pre>\n<h2>Cross-Cutting Concern #3: Caching</h2>\n<p>Caching is an essential cross-cutting concern in software development.\nIt's primarily aimed at enhancing performance and scalability.\nCaching involves temporarily storing data in a fast-access layer.\nThis reduces the need to fetch or calculate the same information repeatedly.</p>\n<p>The caching pipeline behavior, which you see below, implements the <a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside\">Cache Aside pattern.</a>\nThis pattern involves checking the cache before processing the request and updating the cache with new data as needed.\nIt's a popular caching strategy due to its simplicity and effectiveness.\nHere's a <a href=\"https://youtu.be/LOEYZRE72wE\">video tutorial</a> if you want to see how I implemented this.</p>\n<p>When implementing caching, it's crucial to consider:</p>\n<ul>\n<li><strong>What to Cache:</strong> Identify data that is expensive to compute or retrieve and stable enough to be cached.</li>\n<li><strong>Cache Invalidations</strong>: Determine when and how cached data should be invalidated.</li>\n<li><strong>Cache Configuration:</strong> Configure cache settings like expiration and size appropriately.</li>\n</ul>\n<p>Effective caching improves response times and reduces the load on your system, making it a critical strategy for building scalable .NET applications.</p>\n<pre><code class=\"language-csharp\">internal sealed class QueryCachingPipelineBehavior&lt;TRequest, TResponse&gt;(\n    ICacheService cacheService,\n    ILogger&lt;QueryCachingPipelineBehavior&lt;TRequest, TResponse&gt;&gt; logger)\n    : IPipelineBehavior&lt;TRequest, TResponse&gt;\n    where TRequest : ICachedQuery\n    where TResponse : Result\n{\n    public async Task&lt;TResponse&gt; Handle(\n        TRequest request,\n        RequestHandlerDelegate&lt;TResponse&gt; next,\n        CancellationToken cancellationToken)\n    {\n        TResponse? cachedResult = await cacheService.GetAsync&lt;TResponse&gt;(\n            request.CacheKey,\n            cancellationToken);\n\n        string requestName = typeof(TRequest).Name;\n        if (cachedResult is not null)\n        {\n            logger.LogInformation(&quot;Cache hit for {RequestName}&quot;, requestName);\n\n            return cachedResult;\n        }\n\n        logger.LogInformation(&quot;Cache miss for {RequestName}&quot;, requestName);\n\n        TResponse result = await next();\n\n        if (result.IsSuccess)\n        {\n            await cacheService.SetAsync(\n                request.CacheKey,\n                result,\n                request.Expiration,\n                cancellationToken);\n        }\n\n        return result;\n    }\n}\n</code></pre>\n<h2>What To Do Next</h2>\n<p>Managing cross-cutting concerns such as logging, caching, validation, and exception handling is not just about technical implementation.\nIt's about aligning these aspects with the core principles of <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\">Clean Architecture.</a>\nBy adopting the decoupling techniques we discussed, you can ensure that your .NET projects are robust and maintainable.</p>\n<p>Each step you take towards refining your handling of cross-cutting concerns is a step towards a better software architecture.\nI encourage you to experiment with these strategies in your own .NET projects.\nIf you want a structured guide covering these aspects in-depth, take a look at <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\">Pragmatic Clean Architecture.</a></p>\n<p>Remember, the beauty of software development lies in the continuous evolution and relentless pursuit of improvement.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture",
            "title": "Balancing Cross-Cutting Concerns in Clean Architecture",
            "summary": "Cross-cutting concerns are application-level functionalities that affect the entire application and span several layers.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_073.png",
            "date_modified": "2024-01-20T00:00:00.000Z",
            "date_published": "2024-01-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore",
            "content_html": "<p>Delegating handlers are like ASP.NET Core middleware for outgoing HTTP requests.\nYou inherit from the <code>DelegatingHandler</code> base class and override <code>SendAsync</code> to add behavior before or after an <code>HttpClient</code> sends a request.\nThis is useful for cross-cutting concerns like logging, resiliency, and authentication.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=net-8.0\">Delegating handlers</a> are like <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\">ASP.NET Core middleware</a>.\nExcept they work with the <a href=\"https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet\"><code>HttpClient</code></a>.\nThe ASP.NET Core request pipeline allows you to introduce custom behavior with <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\">middleware.</a>\nYou can solve many cross-cutting concerns using middleware — logging, tracing, validation, authentication, authorization, etc.</p>\n<p>But, an important aspect here is that middleware works with incoming HTTP requests to your API.\nDelegating handlers work with outgoing requests.</p>\n<p><a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-8.0\"><code>HttpClient</code></a> is my preferred way of sending HTTP requests in ASP.NET Core.\nIt's straightforward to use and solves most of my use cases.\nYou can use delegating handlers to extend the <code>HttpClient</code> with behavior before or after sending an HTTP request.</p>\n<p>Today, I want to show you how to use a <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=net-8.0\"><code>DelegatingHandler</code></a> to introduce:</p>\n<ul>\n<li>Logging</li>\n<li>Resiliency</li>\n<li>Authentication</li>\n</ul>\n<h2>Configuring an HttpClient</h2>\n<p>Here's a very simple application that:</p>\n<ul>\n<li>Configures the <code>GitHubService</code> class as a typed HTTP client</li>\n<li>Sets the <code>HttpClient.BaseAddress</code> to point to the GitHub API</li>\n<li>Exposes an endpoint that retrieves a GitHub user by their username</li>\n</ul>\n<p>We're going to extend the <code>GitHubService</code> behavior using delegating handlers.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddHttpClient&lt;GitHubService&gt;(httpClient =&gt;\n{\n    httpClient.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n});\n\nvar app = builder.Build();\n\napp.MapGet(&quot;api/users/{username}&quot;, async (\n    string username,\n    GitHubService gitHubService) =&gt;\n{\n    var content = await gitHubService.GetByUsernameAsync(username);\n\n    return Results.Ok(content);\n});\n\napp.Run();\n</code></pre>\n<p>The <code>GitHubService</code> class is a <a href=\"https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet#replacing-named-clients-with-typed-clients\">typed client</a> implementation.\nTyped clients allow you to expose a strongly typed API and hide the <code>HttpClient</code>.\nThe runtime takes care of providing a configured <code>HttpClient</code> instance through dependency injection.\nYou also don't have to think about disposing of the <code>HttpClient</code>.\nIt's resolved from an underlying <strong><code>IHttpClientFactory</code></strong> that manages the <code>HttpClient</code> lifetime.</p>\n<pre><code class=\"language-csharp\">public class GitHubService(HttpClient client)\n{\n    public async Task&lt;GitHubUser?&gt; GetByUsernameAsync(string username)\n    {\n        var url = $&quot;users/{username}&quot;;\n\n        return await client.GetFromJsonAsync&lt;GitHubUser&gt;(url);\n    }\n}\n</code></pre>\n<h2>Logging HTTP Requests Using Delegating Handlers</h2>\n<p>Let's start with a simple example.\nWe will add logging before and after sending an HTTP request.\nFor this, we will to create a custom delegating handler - <code>LoggingDelegatingHandler</code>.</p>\n<p>The custom delegating handler implements the <code>DelegatingHandler</code> base class.\nThen, you can override the <code>SendAsync</code> method to introduce additional behavior.</p>\n<pre><code class=\"language-csharp\">public class LoggingDelegatingHandler(ILogger&lt;LoggingDelegatingHandler&gt; logger)\n    : DelegatingHandler\n{\n    protected override async Task&lt;HttpResponseMessage&gt; SendAsync(\n        HttpRequestMessage request,\n        CancellationToken cancellationToken)\n    {\n        try\n        {\n            logger.LogInformation(&quot;Before HTTP request&quot;);\n\n            var result = await base.SendAsync(request, cancellationToken);\n\n            result.EnsureSuccessStatusCode();\n\n            logger.LogInformation(&quot;After HTTP request&quot;);\n\n            return result;\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, &quot;HTTP request failed&quot;);\n\n            throw;\n        }\n    }\n}\n</code></pre>\n<p>You also need to register the <code>LoggingDelegatingHandler</code> with dependency injection.\nDelegating handlers must be registered as <strong>transient</strong> services.</p>\n<p>The <code>AddHttpMessageHandler</code> method adds the <code>LoggingDelegatingHandler</code> as a delegating handler for the <code>GitHubService</code>.\nAny HTTP request sent using the <code>GitHubService</code> will first go through the <code>LoggingDelegatingHandler</code>.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddTransient&lt;LoggingDelegatingHandler&gt;();\n\nbuilder.Services.AddHttpClient&lt;GitHubService&gt;(httpClient =&gt;\n{\n    httpClient.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n})\n.AddHttpMessageHandler&lt;LoggingDelegatingHandler&gt;();\n</code></pre>\n<p>Let's see what else we can do.</p>\n<h2>Adding Resiliency With Delegating Handlers</h2>\n<p>Building <a href=\"https://learn.microsoft.com/en-us/dotnet/core/resilience/?tabs=dotnet-cli\">resilient</a> applications is an important requirement for cloud development.</p>\n<p>The <code>RetryDelegatingHandler</code> class uses <a href=\"https://github.com/App-vNext/Polly\">Polly</a> to create an <code>AsyncRetryPolicy</code>.\nThe <strong>retry policy</strong> wraps the HTTP request and retries it in case of a transient failure.</p>\n<pre><code class=\"language-csharp\">public class RetryDelegatingHandler : DelegatingHandler\n{\n    private readonly AsyncRetryPolicy&lt;HttpResponseMessage&gt; _retryPolicy =\n        Policy&lt;HttpResponseMessage&gt;\n            .Handle&lt;HttpRequestException&gt;()\n            .RetryAsync(2);\n\n    protected override async Task&lt;HttpResponseMessage&gt; SendAsync(\n        HttpRequestMessage request,\n        CancellationToken cancellationToken)\n    {\n        var policyResult = await _retryPolicy.ExecuteAndCaptureAsync(\n            () =&gt; base.SendAsync(request, cancellationToken));\n\n        if (policyResult.Outcome == OutcomeType.Failure)\n        {\n            throw new HttpRequestException(\n                &quot;Something went wrong&quot;,\n                policyResult.FinalException);\n        }\n\n        return policyResult.Result;\n    }\n}\n</code></pre>\n<p>You also need to register the <code>RetryDelegatingHandler</code> with dependency injection.\nAlso, remember to configure it as a message handler.\nIn this example, I'm chaining two delegating handlers together, and they will run one after another.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddTransient&lt;RetryDelegatingHandler&gt;();\n\nbuilder.Services.AddHttpClient&lt;GitHubService&gt;(httpClient =&gt;\n{\n    httpClient.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n})\n.AddHttpMessageHandler&lt;LoggingDelegatingHandler&gt;()\n.AddHttpMessageHandler&lt;RetryDelegatingHandler&gt;();\n</code></pre>\n<h2>Solving Authentication With Delegating Handlers</h2>\n<p>Authentication is a cross-cutting concern you will have to solve in any microservices application.\nA common use case for delegating handlers is adding the <code>Authorization</code> header before sending an HTTP request.</p>\n<p>For example, the GitHub API requires an access token to be present for authenticating incoming requests.\nThe <code>AuthenticationDelegatingHandler</code> class adds the <code>Authorization</code> header value from the <code>GitHubOptions</code>.\nAnother requirement is specifying the <code>User-Agent</code> header, which is set from the app configuration.</p>\n<pre><code class=\"language-csharp\">public class AuthenticationDelegatingHandler(IOptions&lt;GitHubOptions&gt; options)\n    : DelegatingHandler\n{\n    protected override Task&lt;HttpResponseMessage&gt; SendAsync(\n        HttpRequestMessage request,\n        CancellationToken cancellationToken)\n    {\n        request.Headers.Add(&quot;Authorization&quot;, options.Value.AccessToken);\n        request.Headers.Add(&quot;User-Agent&quot;, options.Value.UserAgent);\n\n        return base.SendAsync(request, cancellationToken);\n    }\n}\n</code></pre>\n<p>Don't forget to configure the <code>AuthenticationDelegatingHandler</code> with the <code>GitHubService</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddTransient&lt;AuthenticationDelegatingHandler&gt;();\n\nbuilder.Services.AddHttpClient&lt;GitHubService&gt;(httpClient =&gt;\n{\n    httpClient.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n})\n.AddHttpMessageHandler&lt;LoggingDelegatingHandler&gt;()\n.AddHttpMessageHandler&lt;RetryDelegatingHandler&gt;()\n.AddHttpMessageHandler&lt;AuthenticationDelegatingHandler&gt;();\n</code></pre>\n<p>Here's a more involved authentication example using the <code>KeyCloakAuthorizationDelegatingHandler</code>.\nThis is a delegating handler that acquires the access token from <a href=\"https://www.keycloak.org/\">Keycloak</a>.\nKeycloak is an open-source identity and access management service.</p>\n<p>I used Keycloak as the identity provider in my <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\">Pragmatic Clean Architecture</a> course.</p>\n<p>The delegating handler in this example uses an <a href=\"https://oauth.net/2/\">OAuth 2.0</a> <a href=\"https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/\">client credentials</a> grant flow to obtain an access token.\nThis grant is used when applications request an access token to access their own resources, not on behalf of a user.</p>\n<pre><code class=\"language-csharp\">public class KeyCloakAuthorizationDelegatingHandler(\n    IOptions&lt;KeycloakOptions&gt; keycloakOptions)\n    : DelegatingHandler\n{\n    protected override async Task&lt;HttpResponseMessage&gt; SendAsync(\n        HttpRequestMessage request,\n        CancellationToken cancellationToken)\n    {\n        var authToken = await GetAccessTokenAsync();\n\n        request.Headers.Authorization = new AuthenticationHeaderValue(\n            JwtBearerDefaults.AuthenticationScheme,\n            authToken.AccessToken);\n\n        var httpResponseMessage = await base.SendAsync(\n            request,\n            cancellationToken);\n\n        httpResponseMessage.EnsureSuccessStatusCode();\n\n        return httpResponseMessage;\n    }\n\n    private async Task&lt;AuthToken&gt; GetAccessTokenAsync()\n    {\n        var params = new KeyValuePair&lt;string, string&gt;[]\n        {\n            new(&quot;client_id&quot;, _keycloakOptions.Value.AdminClientId),\n            new(&quot;client_secret&quot;, _keycloakOptions.Value.AdminClientSecret),\n            new(&quot;scope&quot;, &quot;openid email&quot;),\n            new(&quot;grant_type&quot;, &quot;client_credentials&quot;)\n        };\n\n        var content = new FormUrlEncodedContent(params);\n\n        var authRequest = new HttpRequestMessage(\n            HttpMethod.Post,\n            new Uri(_keycloakOptions.TokenUrl))\n        {\n            Content = content\n        };\n\n        var response = await base.SendAsync(authRequest, cancellationToken);\n\n        response.EnsureSuccessStatusCode();\n\n        return await response.Content.ReadFromJsonAsync&lt;AuthToken&gt;() ??\n               throw new ApplicationException();\n    }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Delegating handlers give you a powerful mechanism to extend the behavior when sending requests with an <code>HttpClient</code>.\nYou can use delegating handlers to solve cross-cutting concerns, similar to how you would use middleware.</p>\n<p>Here are a few ideas on how you could use delegating handlers:</p>\n<ul>\n<li>Logging before and after sending HTTP requests</li>\n<li>Introducing resilience policies (retry, fallback)</li>\n<li>Validating the HTTP request content</li>\n<li>Authenticating with an external API</li>\n</ul>\n<p>I'm sure you can come up with a few use cases yourself.</p>\n<p>I made a video showing how to <a href=\"https://youtu.be/_u6v4D6qgDI\">implement delegating handlers</a>, and you can <a href=\"https://youtu.be/_u6v4D6qgDI\">watch it here.</a></p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore",
            "title": "Extending HttpClient With Delegating Handlers in ASP.NET Core",
            "summary": "Delegating handlers are like ASP.NET Core middleware. Except they work with the HttpClient. I'll show you how to work with delegating handlers",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_072.png",
            "date_modified": "2024-01-13T00:00:00.000Z",
            "date_published": "2024-01-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus",
            "content_html": "<p>MassTransit gives you one messaging abstraction over several transports, so the same publish and consume code runs on RabbitMQ or Azure Service Bus.\nYou install the matching transport package, call <code>AddMassTransit</code>, and select the transport with <code>UsingRabbitMq</code> or <code>UsingAzureServiceBus</code>.\nMassTransit configures the broker topology for you.</p>\n<p><a href=\"https://masstransit.io/\">MassTransit</a> is an open-source distributed application framework for .NET.\nIt provides a messaging abstraction on top of the supported message transports.\nMassTransit lets you focus on adding business value instead of worrying about messaging complexity.</p>\n<p>MassTransit supports many message transport technologies.\nHere are a few that are popular:</p>\n<ul>\n<li>RabbitMQ</li>\n<li>Azure Service Bus</li>\n<li>Amazon SQS</li>\n<li>Kafka</li>\n</ul>\n<p>In today's newsletter, I'll show you how to install and configure MassTransit in .NET.\nWe'll connect MassTransit to a few message brokers - RabbitMQ and Azure Service Bus.\nAnd we will also cover how to publish and consume messages with MassTransit.</p>\n<h2>Why Use MassTransit?</h2>\n<p>MassTransit solves many challenges of building distributed applications.\nYou (almost) don't have to think about the underlying message transport.\nThis allows you to focus on providing business value.</p>\n<p>Here are a few things MassTransit does for you:</p>\n<ul>\n<li><strong>Message routing</strong> - Type-based publish/subscribe, automatic broker topology configuration</li>\n<li><strong>Exception handling</strong> - Messages can be retried or moved to an error queue</li>\n<li><strong>Dependency injection</strong> - Service collection configuration and scope service provider</li>\n<li><strong>Request-Response</strong> - Handle requests with automatic response routing</li>\n<li><strong>Observability</strong> - Native <a href=\"https://opentelemetry.io/\">Open Telemetry (OTEL)</a> support</li>\n<li><strong>Scheduling</strong> - Schedule message delivery using transport delay, Quartz.NET, or Hangfire</li>\n<li><a href=\"https://milanjovanovic.tech/blog/saga-pattern-dotnet\"><strong>Sagas</strong></a> - Reliable, durable, event-driven workflow orchestration</li>\n</ul>\n<p>Let's see how to start using MassTransit.</p>\n<h2>Installing and Configuring MassTransit with RabbitMQ</h2>\n<p>You need to install the <code>MassTransit</code> library.\nIf you already have a message transport, you can install the respective transport library.\nLet's add the <code>MassTransit.RabbitMQ</code> library to configure <a href=\"https://www.rabbitmq.com/\">RabbitMQ</a> as the transport mechanism.</p>\n<pre><code class=\"language-powershell\">Install-Package MassTransit\n\nInstall-Package MassTransit.RabbitMQ\n</code></pre>\n<p>Then, you can configure the required services for <code>MassTransit</code>.\nThe <code>AddMassTransit</code> method accepts a delegate where you can configure many settings.\nFor example, you can set the messaging endpoints to use kebab case naming by calling <code>SetKebabCaseEndpointNameFormatter</code>.\nThis is also where you configure the transport mechanism.\nCalling <code>UsingRabbitMq</code> allows you to connect RabbitMQ as the transport mechanism.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(busConfigurator =&gt;\n{\n    busConfigurator.SetKebabCaseEndpointNameFormatter();\n\n    busConfigurator.UsingRabbitMq((context, configurator) =&gt;\n    {\n        configurator.Host(&quot;localhost&quot;, &quot;/&quot;, h =&gt;\n        {\n            h.Username(&quot;guest&quot;);\n            h.Password(&quot;guest&quot;);\n        });\n\n        configurator.ConfigureEndpoints(context);\n    });\n});\n</code></pre>\n<p>MassTransit takes care of setting up the required broker topology.\nRabbitMQ supports exchanges and queues, so messages are sent or published to exchanges.\nRabbitMQ routes those messages through exchanges to the appropriate queues.</p>\n<p>You can start RabbitMQ locally inside a Docker container:</p>\n<pre><code class=\"language-csharp\">docker run -d --name rabbitmq -p 5672:5672\n</code></pre>\n<h2>Configuring MassTransit with Azure Service Bus</h2>\n<p><a href=\"https://azure.microsoft.com/en-us/products/service-bus\">Azure Service Bus</a>\nis a cloud-based message broker with support for queues and topics.\nMassTransit fully supports Azure Service Bus, including many advanced features and capabilities.\nHowever, you must be on the Standard or Premium tier of the Microsoft Azure Service Bus service.</p>\n<p>To configure MassTransit to work with Azure Service Bus, you need to install the required transport library:</p>\n<pre><code class=\"language-powershell\">Install-Package MassTransit.Azure.ServiceBus.Core\n</code></pre>\n<p>Then, you can connect to Azure Service Bus by calling <code>UsingAzureServiceBus</code> and providing the connection string.\nEverything else remains the same.\nMassTransit takes care of configuring the broker topology.\nMassTransit sends messages to topics, and Azure Service Bus routes those messages to the appropriate queues.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(busConfigurator =&gt;\n{\n    busConfigurator.SetKebabCaseEndpointNameFormatter();\n\n    busConfigurator.UsingAzureServiceBus((context, configurator) =&gt;\n    {\n        configurator.Host(&quot;&lt;CONNECTION_STRING&gt;&quot;);\n\n        configurator.ConfigureEndpoints(context);\n    });\n});\n</code></pre>\n<h2>Using the MassTransit In Memory Transport</h2>\n<p>You can also configure MassTransit to use an in-memory transport.\nIt's useful for testing, because it doesn't require a message broker to be running.\nAnother advantage is that it's fast.</p>\n<p>However, there's a big problem with the in-memory transport - <strong>it's not durable.</strong></p>\n<p>If the message bus is stopped, all messages are lost.\nDon't use the in-memory transport for production systems.</p>\n<p>It will only work on a single machine.\nSo, it doesn't make sense for distributed applications.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(busConfigurator =&gt;\n{\n    busConfigurator.SetKebabCaseEndpointNameFormatter();\n\n    busConfigurator.UsingInMemory((context, configurator) =&gt;\n    {\n        configurator.ConfigureEndpoints(context);\n    });\n});\n</code></pre>\n<h2>Message Types</h2>\n<p>MassTransit requires message types to be <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types\">reference types.</a>\nSo, you can use a <code>class</code>, <code>record</code> or <code>interface</code> to define a message.</p>\n<p>You'll often create two types of messages: commands and events.</p>\n<p>A command is an instruction to perform some action.\nCommands are intended to have exactly one consumer.\nCommands are expressed using verbs first: <code>CreateArticle</code>, <code>PublishArticle</code>, <code>ShareArticle</code>.</p>\n<p>An event represents that something of significance happened.\nEvents can have one or many consumers.\nEvents should have a name in the past tense: <code>ArticleCreated</code>, <code>ArticlePublished</code>, <code>ArticleShared</code>.</p>\n<p>Here's an example <code>ArticleCreated</code> message containing information about an article:</p>\n<pre><code class=\"language-csharp\">public record ArticleCreated\n{\n    public Guid Id { get; init; }\n    public string Title { get; init; }\n    public string Content { get; init; }\n    public DateTime CreatedOnUtc { get; init; }\n}\n</code></pre>\n<p>Using <code>public set</code> or <code>public init</code> properties is recommended to avoid serialization problems with <code>System.Text.Json</code>.</p>\n<h2>Publishing and Consuming Messages</h2>\n<p>You can use the <code>IPublishEndpoint</code> service to publish messages with MassTransit.\nThe framework routes the message to the appropriate queue or topic based on the message type.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;article&quot;, async (\n    CreateArticleRequest request,\n    IPublishEndpoint publishEndpoint) =&gt;\n{\n    await publishEndpoint.Publish(new ArticleCreated\n    {\n        Id = Guid.NewGuid(),\n        Title = request.Title,\n        Content = request.Content,\n        CreatedOnUtc = DateTime.UtcNow\n    });\n\n    return Results.Accepted();\n});\n</code></pre>\n<p>To consume an <code>ArticleCreated</code> message, you need to implement the <code>IConsumer</code> interface.\nThe <code>IConsumer</code> has one <code>Consume</code> method where you place your business logic.\nThe consumer also gives you access to the <code>ConsumeContext</code>, which you can use to send further messages.</p>\n<pre><code class=\"language-csharp\">public class ArticleCreatedConsumer(ApplicationDbContext dbContext)\n    : IConsumer&lt;ArticleCreatedEvent&gt;\n{\n    public async Task Consume(ConsumeContext&lt;ArticleCreatedEvent&gt; context)\n    {\n        ArticleCreated message = context.Message;\n\n        var article = new Article\n        {\n            Id = message.Id,\n            Title = message.Title,\n            Content = message.Content,\n            CreatedOnUtc = message.CreatedOnUtc\n        };\n\n        dbContext.Add(article);\n\n        await dbContext.SaveChangesAsync();\n    }\n}\n</code></pre>\n<p>MassTransit doesn't automatically know that the <code>ArticleCreatedConsumer</code> exists.\nYou have to configure the consumer when calling <code>AddMassTransit</code>.\nThe <code>AddConsumer</code> method registers the consumer type with the bus.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMassTransit(busConfigurator =&gt;\n{\n    busConfigurator.SetKebabCaseEndpointNameFormatter();\n\n    busConfigurator.AddConsumer&lt;ArticleCreatedConsumer&gt;();\n\n    // ...\n});\n</code></pre>\n<h2>Next Steps</h2>\n<p><strong>MassTransit</strong> is an excellent messaging library I often use when building distributed applications.\nThe setup is straightforward, and there are only a few important abstractions.\nYou need to know abstraction for publishing messages (<code>IPublishEndpoint</code>) and consuming messages (<code>IConsumer</code>).\nMassTransit takes care of doing the heavy lifting for you.</p>\n<p>If you aren't already using it, I highly recommend adding MassTransit to your toolbox.</p>\n<p>Here are some more practical <strong>MassTransit learning resources</strong>:</p>\n<ul>\n<li><a href=\"https://youtu.be/NjsoykEOkrk\">Request-Response pattern with MassTransit</a></li>\n<li><a href=\"https://youtu.be/CTKWFMZVIWA\">RabbitMQ and MassTransit tutorial</a></li>\n<li><a href=\"https://youtu.be/MzC0PgYocmk\">Microservices with RabbitMQ</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq\">Saga pattern with RabbitMQ</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\">Orchestration vs Choreography</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\">Messaging with Azure Service Bus</a></li>\n</ul>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/using-masstransit-with-rabbitmq-and-azure-service-bus",
            "title": "Using MassTransit with RabbitMQ and Azure Service Bus",
            "summary": "MassTransit is an open-source distributed application framework for .NET. It provides a messaging abstraction on top of the supported message transports.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_071.png",
            "date_modified": "2024-01-06T00:00:00.000Z",
            "date_published": "2024-01-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore",
            "content_html": "<p>API versioning lets your API evolve without breaking the clients already integrated with it.\nInstead of shipping a breaking change, you publish a new version and let clients migrate on their own schedule.\nIn ASP.NET Core you add it with the <code>Asp.Versioning</code> packages, then read the version from the URL segment, a header, or the query string.</p>\n<p>In the past year, I built and maintained a large public API.\nThe API has dozens of integrations, serving mainly mobile applications.</p>\n<p>When your API is serving so many clients, breaking changes are expensive.</p>\n<p>So, everything I implemented on the public API had to be planned.</p>\n<p>Adding a new field? Forget about it.</p>\n<p>Renaming existing fields? Forget about it.</p>\n<p>If I wanted to introduce breaking changes, I had to version the API.</p>\n<p>Today, I'll show you how to implement API versioning in ASP.NET Core.</p>\n<h2>What Is API Versioning?</h2>\n<p>API versioning is the practice of assigning distinct identifiers to different iterations of an API,\nso that clients can target a specific version and continue working even as the API evolves.</p>\n<p>Without versioning, every change you make to your API is potentially a breaking change for clients already integrated with it.\nWith .NET API versioning, you can introduce a new <code>v2</code> endpoint with the updated behavior while <code>v1</code> continues to work exactly as before.\nClients upgrade on their own schedule, and you maintain full control over which old versions to deprecate and remove.</p>\n<p>This is what makes API versioning essential for any public-facing or long-lived .NET API.\nThe same principles apply whether you're building with ASP.NET Core MVC controllers or <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal APIs</strong></a>.</p>\n<h2>Why API Versioning Matters in .NET APIs</h2>\n<p>API versioning allows your API to evolve independently from the clients using it.</p>\n<p>Introducing breaking changes to your API is a bad user experience.\nAPI versioning gives you a mechanism to avoid exposing breaking changes to clients.\nInstead of making a breaking change, you introduce a new API version.</p>\n<p>What's the definition of a breaking change?</p>\n<p>This isn't an exhaustive list, but a few examples of breaking changes are:</p>\n<ul>\n<li>Removing or renaming APIs or API parameters</li>\n<li>Changing the behavior of existing APIs</li>\n<li>Changing the API response contract</li>\n<li>Changing the <a href=\"https://milanjovanovic.tech/blog/rest-api-http-status-codes\"><strong>API error codes</strong></a></li>\n</ul>\n<p>You can decide what a breaking change means for your API.\nFor example, adding a new field to the response doesn't have to be a breaking change.</p>\n<p>Let's see how to implement API versioning.</p>\n<h2>How to Implement API Versioning in .NET Core</h2>\n<p>Let's start by installing three NuGet packages that we'll need to implement API versioning:</p>\n<ul>\n<li><code>Asp.Versioning.Http</code></li>\n<li><code>Asp.Versioning.Mvc</code></li>\n<li><code>Asp.Versioning.Mvc.ApiExplorer</code></li>\n</ul>\n<pre><code class=\"language-powershell\">Install-Package Asp.Versioning.Http # This is needed for Minimal APIs\nInstall-Package Asp.Versioning.Mvc # This is needed for Controllers\nInstall-Package Asp.Versioning.Mvc.ApiExplorer\n</code></pre>\n<p>This allows us to call <code>AddApiVersioning</code> and provide a delegate to configure the <code>ApiVersioningOptions</code>.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddApiVersioning(options =&gt;\n{\n    options.DefaultApiVersion = new ApiVersion(1);\n    options.ReportApiVersions = true;\n    options.AssumeDefaultVersionWhenUnspecified = true;\n    options.ApiVersionReader = ApiVersionReader.Combine(\n        new UrlSegmentApiVersionReader(),\n        new HeaderApiVersionReader(&quot;X-Api-Version&quot;));\n})\n.AddMvc() // This is needed for controllers\n.AddApiExplorer(options =&gt;\n{\n    options.GroupNameFormat = &quot;'v'V&quot;;\n    options.SubstituteApiVersionInUrl = true;\n});\n</code></pre>\n<p>Here's the explanation for the <code>ApiVersioningOptions</code> properties:</p>\n<ul>\n<li><code>DefaultApiVersion</code> - Sets the default API version. Typically, this will be <code>v1.0</code>.</li>\n<li><code>ReportApiVersions</code> - Reports the supported API versions in the <code>api-supported-versions</code> response header.</li>\n<li><code>AssumeDefaultVersionWhenUnspecified</code> - Uses the <code>DefaultApiVersion</code> when the client didn't provide an explicit version.</li>\n<li><code>ApiVersionReader</code> - Configures how to read the API version specified by the client. The default value is <code>QueryStringApiVersionReader</code>.</li>\n</ul>\n<p>The <code>AddApiExplorer</code> method is helpful if you are using Swagger.\nIt will fix the endpoint routes and substitute the API version route parameter.</p>\n<h2>Types of API Versioning in .NET</h2>\n<p>There are several strategies for .NET API versioning, each with different tradeoffs.</p>\n<h3>URL Versioning</h3>\n<p>URL versioning embeds the version directly in the request path:</p>\n<p><code>GET https://localhost:5001/api/v1/workouts</code></p>\n<p>This is the most explicit approach. The version is visible at a glance, trivial to test in a browser or with curl,\nand is the most widely-used strategy for public .NET APIs.\nThe <code>UrlSegmentApiVersionReader</code> in <code>Asp.Versioning.Http</code> handles this automatically.</p>\n<h3>Header Versioning</h3>\n<p>Header versioning passes the API version via a custom request header:</p>\n<p><code>GET https://localhost:5001/api/workouts</code> with header <code>X-Api-Version: 1</code></p>\n<p>This keeps your URLs clean, but the version is invisible unless clients inspect outgoing headers.\nThe <code>HeaderApiVersionReader</code> supports this strategy.</p>\n<h3>Query String Versioning</h3>\n<p>Query string versioning appends the version as a query parameter:</p>\n<p><code>GET https://localhost:5001/api/workouts?api-version=1</code></p>\n<p>This is the default behavior in <code>Asp.Versioning.Http</code> using <code>QueryStringApiVersionReader</code>.\nIt's easy to use during development and testing without any special tooling.</p>\n<p>There are a few other ways to implement API versioning, such as using the <code>accept</code> or <code>content-type</code> headers, but they aren't used often.</p>\n<p>The <code>Asp.Versioning.Http</code> library provides several <code>IApiVersionReader</code> implementations to support these strategies:</p>\n<ul>\n<li><code>UrlSegmentApiVersionReader</code></li>\n<li><code>HeaderApiVersionReader</code></li>\n<li><code>QueryStringApiVersionReader</code></li>\n<li><code>MediaTypeApiVersionReader</code></li>\n</ul>\n<p><a href=\"https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning\">Microsoft's API versioning guidelines</a>\nsuggest using URL or query string parameter versioning.</p>\n<p>I use URL versioning almost exclusively in the applications I'm developing.</p>\n<h2>Versioning Controllers</h2>\n<p>To implement API versioning in ASP.NET controllers, you have to decorate the controller with the <code>ApiVersion</code> attribute.</p>\n<p>The <code>ApiVersion</code> attribute allows you to specify which API versions that <code>WorkoutsController</code> supports.\nIn this case, the controller supports both <code>v1</code> and <code>v2</code>.\nYou use the <code>MapToApiVersion</code> attribute on the endpoints to specify the concrete API version.</p>\n<p>The route parameter <code>v{v:apiVersion}</code> lets you specify the API version using <code>v1</code> or <code>v2</code> in the URL.</p>\n<pre><code class=\"language-csharp\">[ApiVersion(1)]\n[ApiVersion(2)]\n[ApiController]\n[Route(&quot;api/v{v:apiVersion}/workouts&quot;)]\npublic class WorkoutsController : ControllerBase\n{\n    [MapToApiVersion(1)]\n    [HttpGet(&quot;{workoutId}&quot;)]\n    public IActionResult GetWorkoutV1(Guid workoutId)\n    {\n        return Ok(new GetWorkoutByIdQuery(workoutId).Handle());\n    }\n\n    [MapToApiVersion(2)]\n    [HttpGet(&quot;{workoutId}&quot;)]\n    public IActionResult GetWorkoutV2(Guid workoutId)\n    {\n        return Ok(new GetWorkoutByIdQuery(workoutId).Handle());\n    }\n}\n</code></pre>\n<h2>Deprecating API Versions</h2>\n<p>If you want to deprecate an old API version, you can set the <code>Deprecated</code> property on the <code>ApiVersion</code> attribute.\nThe deprecated API versions will be reported using the <code>api-deprecated-versions</code> response header.</p>\n<pre><code class=\"language-csharp\">[ApiVersion(1, Deprecated = true)]\n[ApiVersion(2)]\n[ApiController]\n[Route(&quot;api/v{v:apiVersion}/workouts&quot;)]\npublic class WorkoutsController : ControllerBase\n{\n}\n</code></pre>\n<h2>Versioning Minimal APIs</h2>\n<p>Versioning Minimal APIs requires you to define an <code>ApiVersionSet</code>, which you'll pass to the endpoints.</p>\n<ul>\n<li><code>NewApiVersionSet</code> - Creates a new <code>ApiVersionSetBuilder</code> that you can use to configure the <code>ApiVersionSet</code>.</li>\n<li><code>HasApiVersion</code> - Indicates that the <code>ApiVersionSet</code> supports the specified <code>ApiVersion</code>.</li>\n<li><code>ReportApiVersions</code>- Indicates that all APIs in the <code>ApiVersionSet</code> will report their versions.</li>\n</ul>\n<p>After creating the <code>ApiVersionSet</code>, you must pass it to a Minimal API endpoint by calling <code>WithApiVersionSet</code>.\nYou can map to an explicit API version by calling <code>MapToApiVersion</code>.</p>\n<pre><code class=\"language-csharp\">ApiVersionSet apiVersionSet = app.NewApiVersionSet()\n    .HasApiVersion(new ApiVersion(1))\n    .HasApiVersion(new ApiVersion(2))\n    .ReportApiVersions()\n    .Build();\n\napp.MapGet(&quot;api/v{version:apiVersion}/workouts/{workoutId}&quot;, async (\n    Guid workoutId,\n    ISender sender,\n    CancellationToken ct) =&gt;\n{\n    var query = new GetWorkoutByIdQuery(workoutId);\n\n    Result&lt;WorkoutResponse&gt; result = await sender.Send(query, ct);\n\n    return result.Match(Results.Ok, CustomResults.Problem);\n})\n.WithApiVersionSet(apiVersionSet)\n.MapToApiVersion(1);\n</code></pre>\n<p>Specifying the <code>ApiVersionSet</code> for each Minimal API endpoint can be cumbersome.\nSo you can define a route group and set the <code>ApiVersionSet</code> only once.\nRoute groups are also practical because they allow you to specify the route prefix.</p>\n<pre><code class=\"language-csharp\">ApiVersionSet apiVersionSet = app.NewApiVersionSet()\n    .HasApiVersion(new ApiVersion(1))\n    .ReportApiVersions()\n    .Build();\n\nRouteGroupBuilder group = app\n    .MapGroup(&quot;api/v{version:apiVersion}&quot;)\n    .WithApiVersionSet(apiVersionSet);\n\ngroup.MapGet(&quot;workouts&quot;, ...);\ngroup.MapGet(&quot;workouts/{workoutId}&quot;, ...);\n</code></pre>\n<h2>API Versioning Best Practices in .NET</h2>\n<p>Here are key practices to keep in mind when implementing API versioning in .NET Core:</p>\n<ul>\n<li><strong>Version from day one.</strong> It's far easier to add API versioning before you have external clients. Even a single version (<code>v1</code>) gives you room to introduce <code>v2</code> later without painful migrations.</li>\n<li><strong>Define what a breaking change is.</strong> Agree as a team on what constitutes a breaking change (removing fields, changing response contracts, modifying error codes), and document it. Adding a new optional field or a new endpoint is typically safe.</li>\n<li><strong>Deprecate, don't delete.</strong> When retiring a version, mark it as deprecated first using <code>[ApiVersion(1, Deprecated = true)]</code>. Give clients time to migrate before removing it entirely.</li>\n<li><strong>Report supported versions.</strong> Set <code>ReportApiVersions = true</code> so that clients can discover available versions via the <code>api-supported-versions</code> response header.</li>\n<li><strong>Keep old versions stable.</strong> Once a version is published, treat it as immutable. New features go into a new version, not the old one.</li>\n<li><strong>Prefer URL versioning for public APIs.</strong> It's the most discoverable, easiest to cache, and works across all HTTP clients without special configuration.</li>\n</ul>\n<h2>Takeaway</h2>\n<p>API versioning is one of the best practices for designing modern APIs.\nConsider implementing API versioning from the first release.\nThis makes it easier for clients to support future API versions.\nAnd it gets your team used to managing breaking changes and versioning the API.</p>\n<p>You can use the <code>Asp.Versioning.Http</code> library to add API versioning in ASP.NET Core.\nDefine the supported API versions, and start using them in your endpoints.</p>\n<p>Remember to agree as a team what represents a breaking change.\nThis should be well documented in the team's API design guidelines.</p>\n<p>My preferred way to implement API versioning is using URL versioning. It's simple and explicit.</p>\n<p>And since this is the last issue for the year, I wish you a happy and prosperous new year.</p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore",
            "title": "API Versioning in ASP.NET Core",
            "summary": "API versioning allows your API to evolve independently from the clients using it. I'll show you how to implement API versioning in ASP.NET Core.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_070.png",
            "date_modified": "2023-12-30T00:00:00.000Z",
            "date_published": "2023-12-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals",
            "content_html": "<p>A value object is an immutable domain type with no identity: it wraps a set of primitive values, enforces their invariants, and compares by value.\nIn .NET you can implement one with a <code>record</code> or a <code>ValueObject</code> base class, then persist it with EF Core owned types or complex types.</p>\n<p><strong>Value Objects</strong> are one of the building blocks of Domain-Driven Design.\nDDD is a software development approach for solving problems in complex domains.</p>\n<p>Value objects encapsulate a set of primitive values and related invariants.\nA few examples of value objects are money and date range objects.\nMoney consists of an amount and currency.\nA date range consists of start and end dates.</p>\n<p>Today, I'll show you some best practices for implementing Value Objects.</p>\n<h2>What are Value Objects?</h2>\n<p>Let's start with the definition from the Domain-Driven Design book:</p>\n<blockquote>\n<p>An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object.\nValue Objects are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.</p>\n</blockquote>\n<p><em>— <a href=\"http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215\">Eric Evans</a></em></p>\n<p>Value objects are different from entities - they don't have a concept of identity.\nThey encapsulate primitive types in the domain and solve <a href=\"https://refactoring.guru/smells/primitive-obsession\">primitive obsession.</a></p>\n<p>There are two main qualities of Value Objects:</p>\n<ul>\n<li>They are immutable</li>\n<li>They have no identity</li>\n</ul>\n<p>Another quality of value objects is structural equality.\nTwo value objects are equal if their values are the same.\nThis quality is the least important in practice.\nHowever, there are cases where you want only some values to determine equality.</p>\n<h2>Implementing Value Objects</h2>\n<p>The most important quality of value objects is immutability.\nThe values of a value object can't change once an object is created.\nIf you want to change an individual value, you need to replace the entire value object.</p>\n<p>Here's a <code>Booking</code> entity with primitive values representing an address and the start and end dates of the booking.</p>\n<pre><code class=\"language-csharp\">public class Booking\n{\n    public string Street { get; init; }\n    public string City { get; init; }\n    public string State { get; init; }\n    public string Country { get; init; }\n    public string ZipCode { get; init; }\n\n    public DateOnly StartDate { get; init; }\n    public DateOnly EndDate { get; init; }\n}\n</code></pre>\n<p>You can replace these primitive values with <code>Address</code> and <code>DateRange</code> value objects.</p>\n<pre><code class=\"language-csharp\">public class Booking\n{\n    public Address Address { get; init; }\n\n    public DateRange Period { get; init; }\n}\n</code></pre>\n<p>But how do you implement value objects?</p>\n<h3>C# Records</h3>\n<p>You can use C# <a href=\"https://milanjovanovic.tech/blog/records-anonymous-types-non-destructive-mutation\">records</a> to represent value objects.\nRecords are immutable by design, and they have structural equality.\nWe want both of these qualities for our value objects.</p>\n<p>For example, you can represent an <code>Address</code> value object using a <code>record</code> with a <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors\">primary constructor.</a>\nThe advantage of this approach is conciseness.</p>\n<pre><code class=\"language-csharp\">public record Address(\n    string Street,\n    string City,\n    string State,\n    string Country,\n    string ZipCode);\n</code></pre>\n<p>However, you lose this advantage when defining a private constructor.\nThis will happen when you want to enforce invariants while creating the value object.\nAnother issue with using records is avoiding value object invariants using the <code>with</code> expression.</p>\n<pre><code class=\"language-csharp\">public record Address\n{\n    private Address(\n        string street,\n        string city,\n        string state,\n        string country,\n        string zipCode)\n    {\n        Street = street;\n        City = city;\n        State = state;\n        Country = country;\n        ZipCode = zipCode;\n    }\n\n    public string Street { get; init; }\n    public string City { get; init; }\n    public string State { get; init; }\n    public string Country { get; init; }\n    public string ZipCode { get; init; }\n\n    public static Result&lt;Address&gt; Create(\n        string street,\n        string city,\n        string state,\n        string country,\n        string zipCode)\n    {\n        // Check if the address is valid\n\n        return new Address(street, city, state, country, zipCode);\n    }\n}\n</code></pre>\n<h3>Base Class</h3>\n<p>The alternative way to implement value objects is with a <code>ValueObject</code> base class.\nThe base class handles structural equality with the <code>GetAtomicValues</code> abstract method.\n<code>ValueObject</code> implementations have to implement this method and define the equality components.</p>\n<p>The advantage of using a <code>ValueObject</code> base class is that it's explicit.\nIt's clear which classes in your domain represent value objects.\nAnother advantage is being able to control the equality components.</p>\n<p>Here's a <code>ValueObject</code> base class I use in my projects:</p>\n<pre><code class=\"language-csharp\">public abstract class ValueObject : IEquatable&lt;ValueObject&gt;\n{\n    public static bool operator ==(ValueObject? a, ValueObject? b)\n    {\n        if (a is null &amp;&amp; b is null)\n        {\n            return true;\n        }\n\n        if (a is null || b is null)\n        {\n            return false;\n        }\n\n        return a.Equals(b);\n    }\n\n    public static bool operator !=(ValueObject? a, ValueObject? b) =&gt;\n        !(a == b);\n\n    public virtual bool Equals(ValueObject? other) =&gt;\n        other is not null &amp;&amp; ValuesAreEqual(other);\n\n    public override bool Equals(object? obj) =&gt;\n        obj is ValueObject valueObject &amp;&amp; ValuesAreEqual(valueObject);\n\n    public override int GetHashCode() =&gt;\n        GetAtomicValues().Aggregate(\n            default(int),\n            (hashcode, value) =&gt;\n                HashCode.Combine(hashcode, value.GetHashCode()));\n\n    protected abstract IEnumerable&lt;object&gt; GetAtomicValues();\n\n    private bool ValuesAreEqual(ValueObject valueObject) =&gt;\n        GetAtomicValues().SequenceEqual(valueObject.GetAtomicValues());\n}\n</code></pre>\n<p>The <code>Address</code> value object implementation would look like this:</p>\n<pre><code class=\"language-csharp\">public sealed class Address : ValueObject\n{\n    public string Street { get; init; }\n    public string City { get; init; }\n    public string State { get; init; }\n    public string Country { get; init; }\n    public string ZipCode { get; init; }\n\n    protected override IEnumerable&lt;object&gt; GetAtomicValues()\n    {\n        yield return Street;\n        yield return City;\n        yield return State;\n        yield return Country;\n        yield return ZipCode;\n    }\n}\n</code></pre>\n<h2>When To Use Value Objects?</h2>\n<p>I use value objects to solve primitive obsession and encapsulate domain invariants.\nEncapsulation is an important aspect of any <a href=\"https://milanjovanovic.tech/blog/domain-layer-clean-architecture\"><strong>domain model</strong></a>.\nYou shouldn't be able to create a value object in an invalid state.</p>\n<p>Value objects also give you type safety.\nTake a look at this method signature:</p>\n<pre><code class=\"language-csharp\">public interface IPricingService\n{\n    decimal Calculate(Apartment apartment, DateOnly start, DateOnly end);\n}\n</code></pre>\n<p>Then, compare it to this method signature, where we added value objects.\nYou can see how the <code>IPricingService</code> with value objects is much more explicit.\nYou also get the benefit of type safety.\nWhen compiling the code, value objects reduce the chance of errors creeping in.</p>\n<pre><code class=\"language-csharp\">public interface IPricingService\n{\n    PricingDetails Calculate(Apartment apartment, DateRange period);\n}\n</code></pre>\n<p>Here are a few more things you should consider to decide if you need value objects:</p>\n<ul>\n<li><strong>Complexity of invariants</strong> - If enforcing complex invariants, consider using value objects</li>\n<li><strong>Number of primitives</strong> - Value objects make sense when encapsulating many primitive values</li>\n<li><strong>Number of duplications</strong> - If you need to enforce invariants only in a few places in the code, you can manage without value objects</li>\n</ul>\n<h2>Persisting Value Objects With EF Core</h2>\n<p>Value objects are part of domain entities, and you need to save them in the database.</p>\n<p>I'll show you how to use EF <a href=\"https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities\">Owned Types</a>\nand <a href=\"https://devblogs.microsoft.com/dotnet/announcing-ef8-rc1/#complex-types-as-value-objects\">Complex Types</a>\nto persist value objects.</p>\n<h3>Owned Types</h3>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities\">Owned Types</a> can be configured by calling the <code>OwnsOne</code> method when configuring the entity.\nThis tells EF to persist the <code>Address</code> and <code>Price</code> value objects to the same table as the <code>Apartment</code> entity.\nThe value objects are represented with additional columns in the <code>apartments</code> table.</p>\n<pre><code class=\"language-csharp\">public void Configure(EntityTypeBuilder&lt;Apartment&gt; builder)\n{\n    builder.ToTable(&quot;apartments&quot;);\n\n    builder.OwnsOne(property =&gt; property.Address);\n\n    builder.OwnsOne(property =&gt; property.Price, priceBuilder =&gt;\n    {\n        priceBuilder.Property(money =&gt; money.Currency)\n            .HasConversion(\n                currency =&gt; currency.Code,\n                code =&gt; Currency.FromCode(code));\n    });\n}\n</code></pre>\n<p>A few more remarks about owned types:</p>\n<ul>\n<li>Owned types have a hidden key value</li>\n<li>No support for optional (nullable) owned types</li>\n<li>Owned collections are supported with <code>OwnsMany</code></li>\n<li>Table splitting allows you to persist owned types separately</li>\n</ul>\n<h3>Complex Types</h3>\n<p><a href=\"https://devblogs.microsoft.com/dotnet/announcing-ef8-rc1/#complex-types-as-value-objects\">Complex Types</a> are a new EF feature available in .NET 8.\nThey aren't identified or tracked by a key value.\nComplex types have to be part of an entity type.</p>\n<p>Complex types are more appropriate for representing value objects with EF.</p>\n<p>Here's how you can configure an <code>Address</code> value object as a complex type:</p>\n<pre><code class=\"language-csharp\">public void Configure(EntityTypeBuilder&lt;Apartment&gt; builder)\n{\n    builder.ToTable(&quot;apartments&quot;);\n\n    builder.ComplexProperty(property =&gt; property.Address);\n}\n</code></pre>\n<p>A few limitations for complex types:</p>\n<ul>\n<li>No support for collections</li>\n<li>No support for nullable values</li>\n</ul>\n<h2>Takeaway</h2>\n<p>Value objects help design a rich domain model.\nYou can use them to solve primitive obsession and encapsulate domain invariants.\nValue objects can reduce errors by preventing the instantiation of invalid domain objects.</p>\n<p>You can use a <code>record</code> or <code>ValueObject</code> base class to represent value objects.\nThis should depend on your specific requirements and the complexity of your domain.\nI use <a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>records</strong></a> by default unless I need some qualities of a <code>ValueObject</code> base class.\nFor example, a base class is practical when you want to control equality components.</p>\n<p>More learning material about value objects:</p>\n<ul>\n<li><a href=\"https://youtu.be/P5CRea21R2E\">Solving primitive obsession with value objects</a></li>\n<li><a href=\"https://youtu.be/LhCD5CUSP6g\">Using EF 8 Complex types for value objects</a></li>\n</ul>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals",
            "title": "Value Objects in .NET (DDD Fundamentals)",
            "summary": "Value Objects are one of the building blocks of Domain-Driven Design. Today, I'll show you some best practices for implementing Value Objects.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_069.png",
            "date_modified": "2023-12-23T00:00:00.000Z",
            "date_published": "2023-12-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging",
            "content_html": "<p>These five Serilog practices make .NET logs easier to search: configure Serilog through the ASP.NET Core configuration system, enable request logging, enrich logs with a <code>CorrelationId</code>, log important application events, and run Seq for local development.\nStructured logs are machine-readable, so you can filter them by properties like <code>CorrelationId</code> instead of scanning text.</p>\n<p><a href=\"https://serilog.net/\">Serilog</a> is a <a href=\"https://milanjovanovic.tech/blog/structured-logging-in-asp-net-core-with-serilog\">structured logging</a> library for .NET.</p>\n<p>It's also my preferred logging library in the projects I'm developing.</p>\n<p>Serilog supports many logging destinations called <a href=\"https://github.com/serilog/serilog/wiki/Provided-Sinks\">Sinks.</a>\nThe log destinations range from console and file sinks to managed logging services such as <a href=\"https://github.com/serilog-contrib/serilog-sinks-applicationinsights\">Application Insights.</a></p>\n<p>Today, I want to share 5 practical tips for better structured logging with Serilog.</p>\n<h2>Use The Configuration System</h2>\n<p>There are two ways you can configure Serilog in ASP.NET Core:</p>\n<ul>\n<li>Fluent API</li>\n<li>Configuration system</li>\n</ul>\n<p>The Fluent API allows you to write code and easily configure Serilog.\nThe downside is you are hardcoding your configuration.\nAny configuration changes require deploying a new version.</p>\n<p>I prefer using the ASP.NET configuration system to set up Serilog.\nThe benefit is you can change the logging configuration without redeploying your application.</p>\n<p>You'll need to install the <code>Serilog.Settings.Configuration</code> library.</p>\n<p>This allows you to configure Serilog using the configuration system:</p>\n<pre><code class=\"language-csharp\">builder.Host.UseSerilog((context, loggerConfig) =&gt;\n    loggerConfig.ReadFrom.Configuration(context.Configuration));\n</code></pre>\n<p>Here's a Serilog configuration with <a href=\"https://github.com/serilog/serilog-sinks-console\">Console</a> and <a href=\"https://github.com/datalust/serilog-sinks-seq\">Seq</a> sinks.\nWe also configure a few <a href=\"https://github.com/serilog/serilog/wiki/Enrichment\">Serilog enrichers</a> to enrich application logs with extra information.</p>\n<pre><code class=\"language-json\">{\n  &quot;Serilog&quot;: {\n    &quot;Using&quot;: [&quot;Serilog.Sinks.Console&quot;, &quot;Serilog.Sinks.Seq&quot;],\n    &quot;MinimumLevel&quot;: {\n      &quot;Default&quot;: &quot;Information&quot;,\n      &quot;Override&quot;: {\n        &quot;Microsoft&quot;: &quot;Information&quot;\n      }\n    },\n    &quot;WriteTo&quot;: [\n      { &quot;Name&quot;: &quot;Console&quot; },\n      {\n        &quot;Name&quot;: &quot;Seq&quot;,\n        &quot;Args&quot;: { &quot;serverUrl&quot;: &quot;http://localhost:5341&quot; }\n      }\n    ],\n    &quot;Enrich&quot;: [&quot;FromLogContext&quot;, &quot;WithMachineName&quot;, &quot;WithThreadId&quot;]\n  }\n}\n</code></pre>\n<h2>Use Serilog Request Logging</h2>\n<p>You can install the <code>Serilog.AspNetCore</code> library to add Serilog logging for the ASP.NET Core request pipeline.\nIt adds ASP.NET's internal operations to the same Serilog sinks as your application events.</p>\n<p>All you need to do is call the <code>UseSerilogRequestLogging</code> method:</p>\n<pre><code class=\"language-csharp\">app.UseSerilogRequestLogging();\n</code></pre>\n<p>The <code>SourceContext</code> for these structured logs is <code>Serilog.AspNetCore.RequestLoggingMiddleware</code>.</p>\n<p>Here's an example structured log produced by this middleware:</p>\n<pre><code class=\"language-json\">{\n  &quot;@t&quot;: &quot;2023-12-16T00:00:00.0000000Z&quot;,\n  &quot;@mt&quot;: &quot;HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms&quot;,\n  &quot;@m&quot;: &quot;HTTP POST /api/users responded 409 in 24.7928 ms&quot;,\n  &quot;@i&quot;: &quot;37aa1435&quot;,\n  &quot;@r&quot;: [&quot;24.7928&quot;],\n  &quot;@tr&quot;: &quot;61a449a8606fdb64e88d6c64b7b7354e&quot;,\n  &quot;@sp&quot;: &quot;163ed90674cb12f6&quot;,\n  &quot;ConnectionId&quot;: &quot;0HMVSP0L8FVEN&quot;,\n  &quot;CorrelationId&quot;: &quot;0HMVSP0L8FVEN:0000000B&quot;,\n  &quot;Elapsed&quot;: 24.792778,\n  &quot;RequestId&quot;: &quot;0HMVSP0L8FVEN:0000000B&quot;,\n  &quot;RequestMethod&quot;: &quot;POST&quot;,\n  &quot;RequestPath&quot;: &quot;/api/users&quot;,\n  &quot;SourceContext&quot;: &quot;Serilog.AspNetCore.RequestLoggingMiddleware&quot;,\n  &quot;StatusCode&quot;: 409\n}\n</code></pre>\n<h2>Enrich Your Logs With CorrelationId</h2>\n<p>How can you track all the logs belonging to the same request?</p>\n<p>You can add a <code>CorrelationId</code> property to your structured logs.</p>\n<p>This also works across multiple applications.\nYou need to pass the <code>CorrelationId</code> using an HTTP header.\nFor example, you could use a custom <code>X-Correlation-Id</code> header.</p>\n<p>In the <code>RequestContextLoggingMiddleware</code>, I'm adding the <code>CorrelationId</code> to the Serilog <a href=\"https://github.com/serilog/serilog/wiki/Enrichment#the-logcontext\"><code>LogContext</code></a>.\nThis will make it available to all logs created during this application request.</p>\n<pre><code class=\"language-csharp\">public class RequestContextLoggingMiddleware\n{\n    private const string CorrelationIdHeaderName = &quot;X-Correlation-Id&quot;;\n    private readonly RequestDelegate _next;\n\n    public RequestContextLoggingMiddleware(RequestDelegate next)\n    {\n        _next = next;\n    }\n\n    public Task Invoke(HttpContext context)\n    {\n        string correlationId = GetCorrelationId(context);\n\n        using (LogContext.PushProperty(&quot;CorrelationId&quot;, correlationId))\n        {\n            return _next.Invoke(context);\n        }\n    }\n\n    private static string GetCorrelationId(HttpContext context)\n    {\n        context.Request.Headers.TryGetValue(\n            CorrelationIdHeaderName, out StringValues correlationId);\n\n        return correlationId.FirstOrDefault() ?? context.TraceIdentifier;\n    }\n}\n</code></pre>\n<p>I like to create an extension method for adding the <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\">middleware.</a>\nThe <code>UseRequestContextLogging</code> method will add the <code>RequestContextLoggingMiddleware</code> to the request pipeline.\nNote that the order of registering middleware is important.\nIf you want the <code>CorrelationId</code> in all your logs, you want to place this middleware at the start.</p>\n<pre><code class=\"language-csharp\">public static IApplicationBuilder UseRequestContextLogging(\n    this IApplicationBuilder app)\n{\n    app.UseMiddleware&lt;RequestContextLoggingMiddleware&gt;();\n\n    return app;\n}\n</code></pre>\n<h2>Log Important Application Events</h2>\n<p>In general, I try to log important events in my application.\nThis includes current request information, errors, failures, unexpected values, branching points, etc.</p>\n<p>I'm a proponent of using the <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\">Result pattern</a> to express application failures.\nSo, having a custom middleware to log request processing results is important.</p>\n<p>Some developers prefer using exceptions to achieve the same functionality.\nI disagree with this.\nUsing exceptions for flow control is a bad practice.\nBut still, don't forget to add a <a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\">global exception handler</a> for unhandled exceptions.</p>\n<p>If you're using the <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\">CQRS pattern with MediatR</a>, you can easily add logging for all application requests.</p>\n<p>In the <code>RequestLoggingPipelineBehavior</code> I'm pushing the <code>Error</code> property to the <code>LogContext</code>.\nThe error object is deconstructed into a JSON value in the structured log.\nThis lets me filter my logs based on the error details.</p>\n<pre><code class=\"language-csharp\">internal sealed class RequestLoggingPipelineBehavior&lt;TRequest, TResponse&gt;\n    : IPipelineBehavior&lt;TRequest, TResponse&gt;\n    where TRequest : class\n    where TResponse : Result\n{\n    private readonly ILogger _logger;\n\n    public RequestLoggingPipelineBehavior(ILogger logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task&lt;TResponse&gt; Handle(\n        TRequest request,\n        RequestHandlerDelegate&lt;TResponse&gt; next,\n        CancellationToken cancellationToken)\n    {\n        string requestName = typeof(TRequest).Name;\n\n        _logger.LogInformation(\n            &quot;Processing request {RequestName}&quot;, requestName);\n\n        TResponse result = await next();\n\n        if (result.IsSuccess)\n        {\n            _logger.LogInformation(\n                &quot;Completed request {RequestName}&quot;, requestName);\n        }\n        else\n        {\n            using (LogContext.PushProperty(&quot;Error&quot;, result.Error, true))\n            {\n                _logger.LogError(\n                    &quot;Completed request {RequestName} with error&quot;, requestName);\n            }\n        }\n\n        return result;\n    }\n}\n</code></pre>\n<h2>Use Seq for Local Development</h2>\n<p><a href=\"https://datalust.co/seq\">Seq</a> is a self-hosted search, analysis, and alerting server built for structured log data.\nIt's free to use for local development.\nIt offers advanced search and filtering capabilities on the structured log data.</p>\n<p>You can spin up a Seq instance in a <a href=\"https://www.docker.com/\">Docker</a> container:</p>\n<pre><code class=\"language-yaml\">version: '3.4'\n\nservices:\n  seq:\n    image: datalust/seq:latest\n    container_name: seq\n    environment:\n      - ACCEPT_EULA=Y\n    ports:\n      - 5341:5341\n      - 8081:80\n</code></pre>\n<p>You can start filtering data when you configure Serilog to write application logs to the Seq instance.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_068/seq.png\" alt=\"Seq log search filtered by correlation ID, showing a failed CreateUserCommand and its SQL query\">\n<h2>Summary</h2>\n<p>Structured logs follow follow the same structure.\nAnd since structured logs are machine-readable, you can search them for specific information.\nStructured logs provide more context and details about application errors.\nThey make it easier to identify and fix problems.</p>\n<p>You can use Serilog's powerful <code>LogContext</code> to enrich your logs with a <code>CorrelationId</code>.\nThis lets you easily track all logs related to a single application request.</p>\n<p>When you have structured logging set up, you'll want to search and analyze your logs.\nSeq is an excellent tool for this that you can use for local development.</p>\n<p>If you want to get started with Seq, check out my <a href=\"https://youtu.be/mT8ZkXafuZk\">beginner Seq tutorial.</a></p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging",
            "title": "5 Serilog Best Practices For Better Structured Logging",
            "summary": "Serilog is a structured logging library for .NET. It's also my preferred logging library in the projects I'm developing.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_068.png",
            "date_modified": "2023-12-16T00:00:00.000Z",
            "date_published": "2023-12-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/modular-monolith-data-isolation",
            "content_html": "<p>Data isolation means each module in a modular monolith reads and writes only its own tables, and other modules reach that data through the module's public API.\nThere are four levels: separate tables, a separate schema per module, a separate database per module, and different persistence.\nStart with schemas and introduce separate databases later if the requirements demand it.</p>\n<p>Modular monoliths are an architectural approach that's becoming very popular.\nThey combine the benefits of modularity and monolithic design.</p>\n<p>Modular monoliths try to solve the shortcomings of monolithic and microservice architectures.</p>\n<p>One problem I often see with monolithic architectures is tight coupling between components.</p>\n<p>This leads to dependencies between different parts of the system.</p>\n<p>Modular monoliths enforce better architectural practices with well-defined <a href=\"https://milanjovanovic.tech/blog/module-boundaries-bounded-contexts\"><strong>module boundaries</strong></a> and <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns\"><strong>communication patterns.</strong></a></p>\n<p>But one aspect you can't overlook is data isolation between modules.</p>\n<p>Data isolation ensures that modules are independent and loosely coupled.</p>\n<p>Today, I will show you four data isolation approaches for modular monoliths:</p>\n<ul>\n<li>Separate table</li>\n<li>Separate schema</li>\n<li>Separate database</li>\n<li>Different persistence</li>\n</ul>\n<h2>Why Is Data Isolation Important?</h2>\n<p>Let's first understand why data isolation is important in a modular monolith architecture.</p>\n<p>A modular monolith has strict rules for data integrity:</p>\n<ul>\n<li>Each module can only access its own tables</li>\n<li>No sharing of tables or objects between modules</li>\n<li>Joins are only allowed between tables of the same module</li>\n</ul>\n<p>Modules inside a modular monolith should be self-contained.\nEach module handles its own data.\nOther modules can access that data using the module's public API.</p>\n<p>What are the benefits of this design?</p>\n<p>Keeping modules isolated from each other promotes modularity and loose coupling.\nIt makes it easier to introduce new changes to the system.\nThere are fewer unintended side effects when components are loosely coupled.</p>\n<p>If you are using a relational database, you can still maintain referential integrity.\nRemoving the foreign keys when extracting tables is not a problem.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_067/monolith_components.png\" alt=\"Tightly coupled monolith components with many cross-component dependencies\">\n<h2>Level 1 - Separate Table</h2>\n<p>The simplest solution is to have no isolation at the database level.\nTables for all modules live inside one database.\nIt's not easy to determine which tables belong to which module.</p>\n<p>I'm only mentioning this approach for the sake of completeness.</p>\n<p>But this approach works fine up to a particular application size.</p>\n<p>However, the more tables you have, the harder it becomes to keep them isolated between modules.</p>\n<p>You can improve this by adding logical isolation between tables.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_067/separate_table.png\" alt=\"Level 1 data isolation: separate module tables in the same schema and relational database\">\n<h2>Level 2 - Separate Schema</h2>\n<p>Grouping related tables in the database is a way to introduce logical isolation.\nYou can implement this using database schemas.\nEach module has a unique schema containing the module's tables.</p>\n<p>Now, it becomes easy to distinguish which module contains which tables.</p>\n<p>An easy way to implement this is using <a href=\"https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application\"><strong>multiple EF Core database contexts.</strong></a></p>\n<p>You can also introduce rules to prevent querying data from other modules.\nFor example, you could implement this using <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\"><strong>architecture tests.</strong></a></p>\n<p>I always start with logical data isolation when building a modular monolith.</p>\n<p>But what if this isn't enough?</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_067/separate_schema.png\" alt=\"Level 2 data isolation: a separate schema per module in the same relational database\">\n<h2>Level 3 - Separate Database</h2>\n<p>The next data isolation level is moving each module's data into separate databases.\nThis approach has more constraints than <a href=\"https://milanjovanovic.tech/blog/schema-per-module-vs-database-per-module\"><strong>data isolation using schemas.</strong></a></p>\n<p>This is the way to go if you need strict data isolation rules between modules.\nBut, the downside is more operational complexity.\nYou have to manage infrastructure for multiple databases.</p>\n<p>However, this is an excellent step toward <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>extracting modules.</strong></a></p>\n<p>First, you move the tables of the module you want to extract into a separate database.\nThis also forces you to solve any database coupling problems between your modules.\nYou're ready to extract the module once you move the tables into a separate database.</p>\n<p>Can we take the module data isolation further?</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_067/separate_db.png\" alt=\"Level 3 data isolation: a separate relational database for each module\">\n<h2>Level 4 - Different Persistence</h2>\n<p>Who says you have to use the same database type for all modules?</p>\n<p>I work with relational (SQL) databases most of the time.\nRelational databases are amazing and solve a wide range of problems.\nBut sometimes, a document or graph database is a much better solution.</p>\n<p>The idea here is similar: you're doing data isolation using separate databases.</p>\n<p>However, you can introduce a different database type to solve specific problems.\nFor example, you can use a relational database for one module.\nAnd a graph or column-store database for another module.\nYou also have to maintain different persistence models in your application.</p>\n<p>This could be a worthwhile tradeoff for your use case.\nBut it takes careful planning.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_067/separate_db_type.png\" alt=\"Level 4 data isolation: document, graph, and relational databases selected per module\">\n<h2>Summary</h2>\n<p>Modular monoliths are excellent if you don't need microservices right away.\nYou develop your application as a monolith with distinct boundaries inside the system.\nYou still have the flexibility to <a href=\"https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps\"><strong>extract modules and move to microservices.</strong></a>\nBut you have faster development speed with a modular monolith.</p>\n<p>Modules have to comply with a few rules.\nThey can only access their own tables.\nThey can't share tables with other modules.\nAnd they can't directly query tables of other modules.\nThese rules help to enforce data isolation between modules.</p>\n<p>But you still have to implement data isolation at the database level.</p>\n<p>There are four options you can choose from:</p>\n<ul>\n<li>Separate table</li>\n<li>Separate schema</li>\n<li>Separate database</li>\n<li>Different persistence</li>\n</ul>\n<p>I always go for logical isolation using schemas.\nIt's easy to implement and helps me understand my boundaries better.\nDepending on the requirements, I can introduce separate databases later.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/modular-monolith-data-isolation",
            "title": "Modular Monolith Data Isolation",
            "summary": "Modular monoliths are an architectural approach that's becoming very popular. They combine the benefits of modularity and monolithic design.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_067.png",
            "date_modified": "2023-12-09T00:00:00.000Z",
            "date_published": "2023-12-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8",
            "content_html": "<p>ASP.NET Core 8 gives you two ways to handle exceptions globally.\nThe old way is custom middleware that wraps the request in a <code>try-catch</code> and returns a <code>ProblemDetails</code> response.\nThe new way is the <code>IExceptionHandler</code> abstraction, registered with <code>AddExceptionHandler</code> and plugged into the pipeline with <code>UseExceptionHandler</code>.</p>\n<p>Exceptions are for exceptional situations.\nI even wrote about <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>avoiding exceptions entirely.</strong></a></p>\n<p>But they will inevitably happen in your applications, and you need to handle them.</p>\n<p>You can implement a global exception handling mechanism or handle only specific exceptions.</p>\n<p>ASP.NET Core gives you a few options on how to implement this. So which one should you choose?</p>\n<p>Today, I want to show you an <em>old</em> and <em>new</em> way to handle exceptions in ASP.NET Core 8.</p>\n<h2>Old Way: Exception Handling Midleware</h2>\n<p>The standard to implement exception handling in ASP.NET Core is using middleware.\nMiddleware allows you to introduce logic before or after executing HTTP requests.\nYou can easily extend this to implement exception handling.\nAdd a <code>try-catch</code> statement in the middleware and return an error HTTP response.</p>\n<p>There are <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\"><strong>3 ways to create middleware</strong></a> in ASP.NET Core:</p>\n<ul>\n<li>Using <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-middleware-with-request-delegates\"><strong>request delegates</strong></a></li>\n<li>By <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-middleware-by-convention\"><strong>convention</strong></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-factory-based-middleware\"><code>IMiddleware</code></a></li>\n</ul>\n<p>The convention-based approach requires you to define an <code>InvokeAsync</code> method.</p>\n<p>Here's an <code>ExceptionHandlingMiddleware</code> defined by convention:</p>\n<pre><code class=\"language-csharp\">public class ExceptionHandlingMiddleware\n{\n    private readonly RequestDelegate _next;\n    private readonly ILogger&lt;ExceptionHandlingMiddleware&gt; _logger;\n\n    public ExceptionHandlingMiddleware(\n        RequestDelegate next,\n        ILogger&lt;ExceptionHandlingMiddleware&gt; logger)\n    {\n        _next = next;\n        _logger = logger;\n    }\n\n    public async Task InvokeAsync(HttpContext context)\n    {\n        try\n        {\n            await _next(context);\n        }\n        catch (Exception exception)\n        {\n            _logger.LogError(\n                exception, &quot;Exception occurred: {Message}&quot;, exception.Message);\n\n            var problemDetails = new ProblemDetails\n            {\n                Status = StatusCodes.Status500InternalServerError,\n                Title = &quot;Server Error&quot;\n            };\n\n            context.Response.StatusCode =\n                StatusCodes.Status500InternalServerError;\n\n            await context.Response.WriteAsJsonAsync(problemDetails);\n        }\n    }\n}\n</code></pre>\n<p>The <code>ExceptionHandlingMiddleware</code> will catch any unhandled exception and return a <a href=\"https://www.rfc-editor.org/rfc/rfc7807.html\">Problem Details</a> response.\nYou can decide how much information you want to return to the caller.\nIn this example, I'm hiding the exception details.</p>\n<p>You also need to add this middleware to the ASP.NET Core request pipeline:</p>\n<pre><code class=\"language-csharp\">app.UseMiddleware&lt;ExceptionHandlingMiddleware&gt;();\n</code></pre>\n<h2>New Way: IExceptionHandler</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-8.0\">ASP.NET Core 8</a>\nintroduces a new <a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.diagnostics.iexceptionhandler?view=aspnetcore-8.0\"><code>IExceptionHandler</code></a>\nabstraction for managing exceptions.\nThe built-in exception handler middleware uses <code>IExceptionHandler</code> implementations to handle exceptions.</p>\n<p>This interface has only one <code>TryHandleAsync</code> method.</p>\n<p><code>TryHandleAsync</code> attempts to handle the specified exception within the ASP.NET Core pipeline.\nIf the exception can be handled, it should return <code>true</code>.\nIf the exception can't be handled, it should return <code>false</code>.\nThis allows you to implement custom exception-handling logic for different scenarios.</p>\n<p>Here's a <code>GlobalExceptionHandler</code> implementation:</p>\n<pre><code class=\"language-csharp\">internal sealed class GlobalExceptionHandler : IExceptionHandler\n{\n    private readonly ILogger&lt;GlobalExceptionHandler&gt; _logger;\n\n    public GlobalExceptionHandler(ILogger&lt;GlobalExceptionHandler&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        _logger.LogError(\n            exception, &quot;Exception occurred: {Message}&quot;, exception.Message);\n\n        var problemDetails = new ProblemDetails\n        {\n            Status = StatusCodes.Status500InternalServerError,\n            Title = &quot;Server error&quot;\n        };\n\n        httpContext.Response.StatusCode = problemDetails.Status.Value;\n\n        await httpContext.Response\n            .WriteAsJsonAsync(problemDetails, cancellationToken);\n\n        return true;\n    }\n}\n</code></pre>\n<h2>Configuring IExceptionHandler Implementations</h2>\n<p>You need two things to add an <code>IExceptionHandler</code> implementation to the ASP.NET Core request pipeline:</p>\n<ol>\n<li>Register the <code>IExceptionHandler</code> service with dependency injection</li>\n<li>Register the <a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.diagnostics.exceptionhandlermiddleware?view=aspnetcore-8.0\"><code>ExceptionHandlerMiddleware</code></a>\nwith the request pipeline</li>\n</ol>\n<p>You call the <code>AddExceptionHandler</code> method to register the <code>GlobalExceptionHandler</code> as a service.\nIt's registered with a <a href=\"https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor\"><strong>singleton lifetime</strong></a>.\nSo be careful about injecting services with a different lifetime.</p>\n<p>I'm also calling <code>AddProblemDetails</code> to generate a <a href=\"https://www.rfc-editor.org/rfc/rfc7807.html\">Problem Details</a> response for common exceptions.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();\nbuilder.Services.AddProblemDetails();\n</code></pre>\n<p>You also need to call <code>UseExceptionHandler</code> to add the <code>ExceptionHandlerMiddleware</code> to the request pipeline:</p>\n<pre><code class=\"language-csharp\">app.UseExceptionHandler();\n</code></pre>\n<h2>Chaining Exception Handlers</h2>\n<p>You can add multiple <code>IExceptionHandler</code> implementations, and they're called in the order they are registered.\nA possible use case for this is using exceptions for flow control.</p>\n<p>You can define custom exceptions like <code>BadRequestException</code> and <code>NotFoundException</code>.\nThey correspond with the <a href=\"https://milanjovanovic.tech/blog/rest-api-http-status-codes\"><strong>HTTP status code</strong></a> you would return from the API.</p>\n<p>Here's a <code>BadRequestExceptionHandler</code> implementation:</p>\n<pre><code class=\"language-csharp\">internal sealed class BadRequestExceptionHandler : IExceptionHandler\n{\n    private readonly ILogger&lt;BadRequestExceptionHandler&gt; _logger;\n\n    public BadRequestExceptionHandler(ILogger&lt;BadRequestExceptionHandler&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        if (exception is not BadRequestException badRequestException)\n        {\n            return false;\n        }\n\n        _logger.LogError(\n            badRequestException,\n            &quot;Exception occurred: {Message}&quot;,\n            badRequestException.Message);\n\n        var problemDetails = new ProblemDetails\n        {\n            Status = StatusCodes.Status400BadRequest,\n            Title = &quot;Bad Request&quot;,\n            Detail = badRequestException.Message\n        };\n\n        httpContext.Response.StatusCode = problemDetails.Status.Value;\n\n        await httpContext.Response\n            .WriteAsJsonAsync(problemDetails, cancellationToken);\n\n        return true;\n    }\n}\n</code></pre>\n<p>And here's a <code>NotFoundExceptionHandler</code> implementation:</p>\n<pre><code class=\"language-csharp\">internal sealed class NotFoundExceptionHandler : IExceptionHandler\n{\n    private readonly ILogger&lt;NotFoundExceptionHandler&gt; _logger;\n\n    public NotFoundExceptionHandler(ILogger&lt;NotFoundExceptionHandler&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    public async ValueTask&lt;bool&gt; TryHandleAsync(\n        HttpContext httpContext,\n        Exception exception,\n        CancellationToken cancellationToken)\n    {\n        if (exception is not NotFoundException notFoundException)\n        {\n            return false;\n        }\n\n        _logger.LogError(\n            notFoundException,\n            &quot;Exception occurred: {Message}&quot;,\n            notFoundException.Message);\n\n        var problemDetails = new ProblemDetails\n        {\n            Status = StatusCodes.Status404NotFound,\n            Title = &quot;Not Found&quot;,\n            Detail = notFoundException.Message\n        };\n\n        httpContext.Response.StatusCode = problemDetails.Status.Value;\n\n        await httpContext.Response\n            .WriteAsJsonAsync(problemDetails, cancellationToken);\n\n        return true;\n    }\n}\n</code></pre>\n<p>You also need to register both exception handlers by calling <code>AddExceptionHandler</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddExceptionHandler&lt;BadRequestExceptionHandler&gt;();\nbuilder.Services.AddExceptionHandler&lt;NotFoundExceptionHandler&gt;();\n</code></pre>\n<p>The <code>BadRequestExceptionHandler</code> will execute first and try to handle the exception.\nIf the exception isn't handled, <code>NotFoundExceptionHandler</code> will execute next and attempt to handle the exception.</p>\n<h2>Takeaway</h2>\n<p>Using middleware for exception handling is an excellent solution in ASP.NET Core.\nHowever, it's great that we have new options using the <code>IExceptionHandler</code> interface.\nI will use the new approach in ASP.NET Core 8 projects.</p>\n<p>I'm very much against using exceptions for flow control.\nExceptions are a last resort when you can't continue normal application execution.\nThe <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>Result pattern</strong></a> is a better alternative.</p>\n<p>Exceptions are also <a href=\"https://github.com/dotnet/aspnetcore/issues/46280#issuecomment-1527898867\">extremely expensive</a>,\nas David Fowler noted:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_066/fowler_comment.png\" alt=\"David Fowler explaining that exceptions are extremely expensive in the ASP.NET Core pipeline\">\n<p>If you want to get rid of exceptions in your code, <a href=\"https://youtu.be/WCCkEe_Hy2Y\"><strong>check out this video.</strong></a></p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8",
            "title": "Global Error Handling in ASP.NET Core 8",
            "summary": "Exceptions are for exceptional situations, but they will inevitably happen and you need to handle them.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_066.png",
            "date_modified": "2023-12-02T00:00:00.000Z",
            "date_published": "2023-12-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips",
            "content_html": "<p>Refactoring restructures existing code without changing its behavior, one small transformation at a time.\nI take a nearly 100-line <code>AddCustomer</code> method and apply five techniques to it: extract method, extract interface, extract class, refactoring toward functional code, and pushing logic down into the domain.</p>\n<p>Refactoring is a technique for restructuring existing code without changing its behavior.\nYou can think of refactoring as a series of small code transformations.</p>\n<p>One change (refactoring) does little.\nBut a sequence of refactors produces a significant transformation.</p>\n<p>There's no better way to learn refactoring than practicing.</p>\n<p>So I prepared a refactoring exercise for you.</p>\n<p>Today I'm going to refactor some poorly written code.</p>\n<p>And I'll show you 5 awesome refactoring techniques along the way:</p>\n<ul>\n<li>Extract method</li>\n<li>Extract interface</li>\n<li>Extract class</li>\n<li>Functional code</li>\n<li>Pushing logic down</li>\n</ul>\n<h2>Starting Point</h2>\n<p>We will refactor the <code>CustomerService</code> below to try to improve the code.</p>\n<p>I want to achieve three goals with this refactor.</p>\n<p>Or rather, I want to improve three qualities of the <code>CustomerService</code>:</p>\n<ul>\n<li>Testability</li>\n<li>Readability</li>\n<li>Maintainability</li>\n</ul>\n<p>To improve these qualities, we need to figure out what's preventing us from attaining them.</p>\n<p>So, let's first understand what the <code>CustomerService</code> is doing on a high level:</p>\n<ul>\n<li>Validation of the input arguments</li>\n<li>Fetching the <code>Company</code> and creating a new <code>Customer</code></li>\n<li>Calculating if the <code>Customer</code> has a credit limit and the amount</li>\n<li>Saving the <code>Customer</code> to the database if they meet a specific condition</li>\n</ul>\n<p>You can see quite a few things are happening inside the <code>AddCustomer</code> method.</p>\n<p>It's almost 100 lines of code, which reduces readability.</p>\n<p>It's difficult to test because we can't control any external dependencies.</p>\n<p>It's impossible to extend the behavior of the <code>CustomerService</code> without changing the code.</p>\n<p>But we can fix all these problems.\nLet me show you how.</p>\n<pre><code class=\"language-csharp\">public class CustomerService\n{\n    public bool AddCustomer(\n        string firstName,\n        string lastName,\n        string email,\n        DateTime dateOfBirth,\n        int companyId)\n    {\n        if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))\n        {\n            return false;\n        }\n\n        if (!email.Contains('@') &amp;&amp; !email.Contains('.'))\n        {\n            return false;\n        }\n\n        var now = DateTime.Now;\n        var age = now.Year - dateOfBirth.Year;\n        if (dateOfBirth.Date &gt; now.AddYears(-age))\n        {\n            age -= 1;\n        }\n\n        if (age &lt; 21)\n        {\n            return false;\n        }\n\n        var companyRepository = new CompanyRepository();\n        var company = companyRepository.GetById(companyId);\n\n        var customer = new Customer\n        {\n            Company = company,\n            DateOfBirth = dateOfBirth,\n            EmailAddress = email,\n            Firstname = firstName,\n            Surname = lastName\n        };\n\n        if (company.Type == &quot;VeryImportantClient&quot;)\n        {\n            // Skip credit check\n            customer.HasCreditLimit = false;\n        }\n        else if (company.Type == &quot;ImportantClient&quot;)\n        {\n            // Do credit check and double credit limit\n            customer.HasCreditLimit = true;\n            using var creditService = new CustomerCreditServiceClient();\n\n            var creditLimit = creditService.GetCreditLimit(\n                customer.Firstname,\n                customer.Surname,\n                customer.DateOfBirth);\n\n            creditLimit *= 2;\n            customer.CreditLimit = creditLimit;\n        }\n        else\n        {\n            // Do credit check\n            customer.HasCreditLimit = true;\n            using var creditService = new CustomerCreditServiceClient();\n\n            var creditLimit = creditService.GetCreditLimit(\n                customer.Firstname,\n                customer.Surname,\n                customer.DateOfBirth);\n\n            customer.CreditLimit = creditLimit;\n        }\n\n        if (customer.HasCreditLimit &amp;&amp; customer.CreditLimit &lt; 500)\n        {\n            return false;\n        }\n\n        var customerRepository = new CustomerRepository();\n        customerRepository.AddCustomer(customer);\n\n        return true;\n    }\n}\n</code></pre>\n<h2>Refactoring the Validation</h2>\n<p>The first part of the code validating input parameters is pretty concise.\nIt also follows the early return principle.</p>\n<p>The validation consists of simple input validation and a calculation for the customer's age.</p>\n<p>I would start with an <strong>extract method</strong> refactor to move the validation into one place.</p>\n<pre><code class=\"language-csharp\">if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))\n{\n    return false;\n}\n\nif (!email.Contains('@') &amp;&amp; !email.Contains('.'))\n{\n    return false;\n}\n\nvar now = DateTime.Now;\nvar age = now.Year - dateOfBirth.Year;\nif (dateOfBirth.Date &gt; now.AddYears(-age))\n{\n    age -= 1;\n}\n\nif (age &lt; 21)\n{\n    return false;\n}\n</code></pre>\n<p>Calculating the age isn't part of the validation flow, so I'll extract that into the <code>CalculateAge</code> method.</p>\n<pre><code class=\"language-csharp\">int CalculateAge(DateTime dateOfBirth, DateTime now)\n{\n    var age = now.Year - dateOfBirth.Year;\n    if (dateOfBirth.Date &gt; now.AddYears(-age))\n    {\n        age -= 1;\n    }\n\n    return age;\n}\n</code></pre>\n<p>Then, I'll create the <code>IsValid</code> method to encapsulate all the validation rules.\nInstead of writing many <code>if-else</code> statements, I can write a single <code>bool</code> expression.</p>\n<p>I also introduced a <code>minimumAge</code> constant to improve readability.</p>\n<p>You can see how the <code>CalculateAge</code> method helps simplify the validation check.</p>\n<pre><code class=\"language-csharp\">bool IsValid(\n    string firstName,\n    string lastName,\n    string email,\n    DateTime dateOfBirth)\n{\n    const int minimumAge = 21;\n\n    return !string.IsNullOrEmpty(firstName) &amp;&amp;\n           !string.IsNullOrEmpty(lastName) &amp;&amp;\n           (email.Contains('@') || email.Contains('.')) &amp;&amp;\n           CalculateAge(dateOfBirth, DateTime.Now) &gt;= minimumAge;\n}\n</code></pre>\n<p>This simplifies the validation code in <code>AddCustomer</code> to:</p>\n<pre><code class=\"language-csharp\">if (!IsValid(firstName, lastName, email, dateOfBirth))\n{\n    return false;\n}\n</code></pre>\n<h2>Refactoring Towards Dependency Injection</h2>\n<p>The next problem I want to solve is introducing <a href=\"https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor\">dependency injection</a> to the <code>CustomerService</code>.</p>\n<p>Dependency injection allows us to achieve <strong>Inversion of Control (IoC).</strong>\nWe depend only on interfaces at compile time and on implementations at run time.</p>\n<p>The dependency injection pattern has a few important benefits.</p>\n<p>You don't have to know how to initialize or dispose of external dependencies.</p>\n<p>It also improves testability since you now depend on interfaces.\nInterfaces can be mocked to make unit testing easier.</p>\n<p>So let's update the <code>CustomerService</code> not to initialize the dependencies directly:</p>\n<pre><code class=\"language-csharp\">var companyRepository = new CompanyRepository();\n\nusing var creditService = new CustomerCreditServiceClient();\n\nvar customerRepository = new CustomerRepository();\n</code></pre>\n<p>Instead, we will inject them as constructor arguments.\nYou can even use the <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors\">C# 12 primary constructor</a> feature.</p>\n<pre><code class=\"language-csharp\">public class CustomerService(\n    CompanyRepository companyRepository,\n    CustomerRepository customerRepository,\n    CustomerCreditServiceClient creditService)\n{\n    // ...\n}\n</code></pre>\n<p>The next step would be introducing interfaces for these dependencies.\nThis comes down to an <strong>extract interface</strong> refactor.</p>\n<h2>Refactoring the Credit Limit Calculation</h2>\n<p>The credit limit calculation is the most complicated part of the code.\nThere are different business rules based on the company type.</p>\n<p>I try to notice patterns in the code before refactoring.\nSo here are a few of my observations.</p>\n<p>Multiple <code>if-else</code> statements based on the <code>Type</code> property make me wonder if I'll need to extend this in the future.\nAdding a new rule would mean another <code>if-else</code> check.\nThe <a href=\"https://refactoring.guru/design-patterns/strategy\">strategy pattern</a> could be an alternative, but a <code>switch</code> statement will also work fine.</p>\n<p>Another thing that stands out is the <strong>code duplication</strong> in the last two blocks.\nThis usually means I can do an <strong>extract method</strong> refactoring to reduce code duplication.</p>\n<pre><code class=\"language-csharp\">if (company.Type == &quot;VeryImportantClient&quot;)\n{\n    // Skip credit check\n    customer.HasCreditLimit = false;\n}\nelse if (company.Type == &quot;ImportantClient&quot;)\n{\n    // Do credit check and double credit limit\n    customer.HasCreditLimit = true;\n    using var creditService = new CustomerCreditServiceClient();\n\n    var creditLimit = creditService.GetCreditLimit(\n        customer.Firstname,\n        customer.Surname,\n        customer.DateOfBirth);\n\n    creditLimit *= 2;\n    customer.CreditLimit = creditLimit;\n}\nelse\n{\n    // Do credit check\n    customer.HasCreditLimit = true;\n    using var creditService = new CustomerCreditServiceClient();\n\n    var creditLimit = creditService.GetCreditLimit(\n        customer.Firstname,\n        customer.Surname,\n        customer.DateOfBirth);\n\n    customer.CreditLimit = creditLimit;\n}\n</code></pre>\n<p>The first thing I want to do is introduce an <code>enum</code> for the <code>CompanyType</code>.</p>\n<p>This is a <a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code\">clean coding principle</a> I often use.\nIt improves the readability and extensibility of the code.</p>\n<pre><code class=\"language-csharp\">public enum CompanyType\n{\n    Regular = 0,\n    ImportantClient = 1,\n    VeryImportantClient = 2\n}\n</code></pre>\n<p>The next thing that bothers me is that the credit limit calculation doesn't belong to the <code>CustomerService</code>.\nIt violates the <a href=\"https://en.wikipedia.org/wiki/Single-responsibility_principle\">single responsibility principle</a>.</p>\n<p>So I want to introduce a dedicated <code>CreditLimitCalculator</code> using an <strong>extract class</strong> refactoring.\nI replaced the <code>if-else</code> statements with a <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression\">switch expression</a> that I can easily extend in the future.</p>\n<pre><code class=\"language-csharp\">public class CreditLimitCalculator(\n    CustomerCreditServiceClient customerCreditServiceClient)\n{\n    public (bool HasCreditLimit, decimal? CreditLimit) Calculate(\n        Customer customer,\n        Company company)\n    {\n        return company.Type switch\n        {\n            CompanyType.VeryImportantClient =&gt; (false, null),\n            CompanyType.ImportantClient =&gt; (true, GetCreditLimit(customer) * 2),\n            _ =&gt; (true, GetCreditLimit(customer))\n        };\n    }\n\n    private decimal GetCreditLimit(Customer customer)\n    {\n        return customerCreditServiceClient.GetCreditLimit(\n            customer.FirstName,\n            customer.LastName,\n            customer.DateOfBirth);\n    }\n}\n</code></pre>\n<h2>Reviewing the Refactoring (So Far)</h2>\n<p>Let's pause momentarily and review the refactored version of the <code>CustomerService</code>.\nI'm confident you will find it more readable and easier to understand.\nWe can easily test this class and verify that the behavior is correct.</p>\n<p>I would usually stop the refactoring at this point, since I'm happy with the results.</p>\n<p>But can we take this further?</p>\n<pre><code class=\"language-csharp\">public class CustomerService(\n    CompanyRepository companyRepository,\n    CustomerRepository customerRepository,\n    CreditLimitCalculator creditLimitCalculator)\n{\n    public bool AddCustomer(\n        string firstName,\n        string lastName,\n        string email,\n        DateTime dateOfBirth,\n        int companyId)\n    {\n        if (!IsValid(firstName, lastName, email, dateOfBirth))\n        {\n            return false;\n        }\n\n        var company = companyRepository.GetById(companyId);\n\n        var customer = new Customer\n        {\n            Company = company,\n            DateOfBirth = dateOfBirth,\n            EmailAddress = email,\n            FirstName = firstName,\n            LastName = lastName\n        };\n\n        (customer.HasCreditLimit, customer.CreditLimit) =\n            creditLimitCalculator.Calculate(customer, company);\n\n        if (customer is { HasCreditLimit: true, CreditLimit: &lt; 500 })\n        {\n            return false;\n        }\n\n        customerRepository.AddCustomer(customer);\n\n        return true;\n    }\n}\n</code></pre>\n<h2>Taking It Further - Pushing Logic Down</h2>\n<p>This part is optional, but I want to show you how to simplify the <code>CustomerService</code> by <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>pushing logic into the domain</strong></a>.</p>\n<p>What if we moved the responsibility of creating a <code>Customer</code> into the class?</p>\n<p>I often use the <strong>static factory</strong> pattern to implement this.\nThe caveat is I have to take a dependency on <code>CreditLimitCalculator</code>.\nI'm trading off domain model purity to get business rules completeness.</p>\n<p>I also added the <code>IsUnderCreditLimit</code> method to wrap the credit limit check.</p>\n<pre><code class=\"language-csharp\">public class Customer\n{\n    // Properties omited\n\n    public static Customer Create(\n        Company company,\n        string firstName,\n        string lastName,\n        string email,\n        DateTime dateOfBirth,\n        CreditLimitCalculator creditLimitCalculator)\n    {\n        var customer = new Customer\n        {\n            Company = company,\n            DateOfBirth = dateOfBirth,\n            EmailAddress = email,\n            FirstName = firstName,\n            LastName = lastName\n        };\n\n        (customer.HasCreditLimit, customer.CreditLimit) =\n            creditLimitCalculator.Calculate(customer, company);\n\n        return customer;\n    }\n\n    public bool IsUnderCreditLimit() =&gt; HasCreditLimit &amp;&amp; CreditLimit &lt; 500;\n}\n</code></pre>\n<p>This is what the <code>CustomerService</code> looks like now:</p>\n<pre><code class=\"language-csharp\">public class CustomerService(\n    CompanyRepository companyRepository,\n    CustomerRepository customerRepository,\n    CreditLimitCalculator creditLimitCalculator)\n{\n    public bool AddCustomer(\n        string firstName,\n        string lastName,\n        string email,\n        DateTime dateOfBirth,\n        int companyId)\n    {\n        if (!IsValid(firstName, lastName, email, dateOfBirth))\n        {\n            return false;\n        }\n\n        var company = companyRepository.GetById(companyId);\n\n        var customer = Customer.Create(\n            company,\n            firstName,\n            lastName,\n            email,\n            dateOfBirth,\n            creditLimitCalculator);\n\n        if (customer.IsUnderCreditLimit())\n        {\n            return false;\n        }\n\n        customerRepository.AddCustomer(customer);\n\n        return true;\n    }\n}\n</code></pre>\n<p>What do you think about this implementation?</p>\n<h2>Next Steps</h2>\n<p>First of all, congrats on making it to the end.\nThis was a much longer newsletter issue than usual.\nWhat do you think of this format?</p>\n<p>Writing <a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>unit tests</strong></a> before starting the refactoring would be a great idea.\nUnit tests will help detect any changes in behavior.</p>\n<p>Remember, refactoring is transforming the existing code without changing the behavior.</p>\n<p>Here are a few ideas on how you could further refactor the code:</p>\n<ul>\n<li><a href=\"https://refactoring.guru/design-patterns/strategy\"><strong>Strategy pattern</strong></a> for the credit limit calculation</li>\n<li><a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>Result object</strong></a> to represent the method status</li>\n</ul>\n<p>If you want to try this refactoring exercise, you can find the complete <a href=\"https://github.com/m-jovanovic/refactoring-katas\"><strong>source code here.</strong></a></p>\n<p>Hope this was helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips",
            "title": "5 Awesome C# Refactoring Tips",
            "summary": "Refactoring is restructuring existing code without changing its behavior. One change does little, but a sequence of refactors produces a significant…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_065.png",
            "date_modified": "2023-11-25T00:00:00.000Z",
            "date_published": "2023-11-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors",
            "content_html": "<p><strong>EF Core interceptors</strong> let you intercept, change, or suppress EF Core operations.\nEvery interceptor implements the <code>IInterceptor</code> interface.\nThe most popular one is the <code>SaveChangesInterceptor</code>, which adds behavior before or after saving changes to the database.</p>\n<p>EF Core is my favorite ORM for .NET applications.\nYet, its many fantastic features sometimes go unnoticed.\nFor example, <a href=\"https://milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting\"><strong>query splitting</strong></a>,\n<a href=\"https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core\"><strong>query filters</strong></a>,\nand interceptors.</p>\n<p>EF interceptors are interesting because you can do powerful things with them.\nFor example, you can hook into materialization, handle optimistic concurrency errors, or add query hints.</p>\n<p>The most practical use case is adding behavior when saving changes to the database.</p>\n<p>Today I want to show you three unique use cases for EF Core interceptors:</p>\n<ul>\n<li>Audit logging</li>\n<li>Publishing domain events</li>\n<li>Persisting Outbox messages</li>\n</ul>\n<h2>What are EF Interceptors?</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors\">EF Core interceptors</a>\nallow you to intercept, change, or suppress EF Core operations.\nEvery interceptor implements the <code>IInterceptor</code> interface.\nA few common derived interfaces include <code>IDbCommandInterceptor</code>, <code>IDbConnectionInterceptor</code>, and <code>IDbTransactionInterceptor</code>.</p>\n<p>The most popular one is the <code>ISaveChangesInterceptor</code>. It allows you to add behavior before or after saving changes.</p>\n<p>Interceptors are registered for each <code>DbContext</code> instance when configuring the context.</p>\n<pre><code class=\"language-csharp\">public interface IInterceptor\n{\n}\n</code></pre>\n<p>You don't have to implement these interfaces directly.\nIt's better to use concrete implementations and override the needed methods.</p>\n<p>For example, I'll show you how to use the <code>SaveChangesInterceptor</code>.</p>\n<h2>Audit Logging With EF Interceptors</h2>\n<p>An audit log of entity changes is a valuable feature in some applications.\nYou write additional audit information every time an entity is created or modified.\nThe audit log could also contain the complete before/after values, depending on your requirements.</p>\n<p>However, let's use a simple example to make it easy to understand.</p>\n<p>I have an <code>IAuditable</code> interface with two properties representing when an entity was created or modified.</p>\n<pre><code class=\"language-csharp\">public interface IAuditable\n{\n    DateTime CreatedOnUtc { get; }\n\n    DateTime? ModifiedOnUtc { get; }\n}\n</code></pre>\n<p>Next, I'll implement an <code>UpdateAuditableInterceptor</code> interceptor to write the audit values.\nIt uses the <code>ChangeTracker</code> to find all <code>IAuditable</code> instances and sets the respective property value.</p>\n<p>I want to highlight that I'm overriding the <code>SavingChangesAsync</code> method here.\n<code>SavingChangesAsync</code> runs before the changes are saved in the database and any updates applied inside the <code>UpdateAuditableInterceptor</code>\nare also part of the current database transaction.</p>\n<p>This implementation can be easily extended to include the information about the current user.</p>\n<pre><code class=\"language-csharp\">internal sealed class UpdateAuditableInterceptor : SaveChangesInterceptor\n{\n    public override ValueTask&lt;InterceptionResult&lt;int&gt;&gt; SavingChangesAsync(\n        DbContextEventData eventData,\n        InterceptionResult&lt;int&gt; result,\n        CancellationToken cancellationToken = default)\n    {\n        if (eventData.Context is not null)\n        {\n            UpdateAuditableEntities(eventData.Context);\n        }\n\n        return base.SavingChangesAsync(eventData, result, cancellationToken);\n    }\n\n    private static void UpdateAuditableEntities(DbContext context)\n    {\n        DateTime utcNow = DateTime.UtcNow;\n        var entities = context.ChangeTracker.Entries&lt;IAuditable&gt;().ToList();\n\n        foreach (EntityEntry&lt;IAuditable&gt; entry in entities)\n        {\n            if (entry.State == EntityState.Added)\n            {\n                SetCurrentPropertyValue(\n                    entry, nameof(IAuditable.CreatedOnUtc), utcNow);\n            }\n\n            if (entry.State == EntityState.Modified)\n            {\n                SetCurrentPropertyValue(\n                    entry, nameof(IAuditable.ModifiedOnUtc), utcNow);\n            }\n        }\n\n        static void SetCurrentPropertyValue(\n            EntityEntry entry,\n            string propertyName,\n            DateTime utcNow) =&gt;\n            entry.Property(propertyName).CurrentValue = utcNow;\n    }\n}\n</code></pre>\n<h2>Publish Domain Events With EF Interceptors</h2>\n<p>Another use case for EF interceptors is <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>publishing domain events.</strong></a>\nDomain events are a DDD tactical pattern to create loosely coupled systems.</p>\n<p>Domain events allow you to express side effects explicitly and provide a better separation of concerns in the domain.</p>\n<p>You can create an <code>IDomainEvent</code> interface, which derives from <code>MediatR.INotification</code>.\nThis allows you to use the <code>IPublisher</code> to publish domain events and handle them asynchronously.</p>\n<pre><code class=\"language-csharp\">using MediatR;\n\npublic interface IDomainEvent : INotification\n{\n}\n</code></pre>\n<p>Then, I'll create a <code>PublishDomainEventsInterceptor</code> that also inherits from <code>SaveChangesInterceptor</code>.\nHowever, this time, we're using the <code>SavedChangesAsync</code> to publish the domain events <em>after</em> saving changes in the database.</p>\n<p>This has two important implications:</p>\n<ol>\n<li>The entire workflow is now eventually consistent. Domain event handlers will save changes to the database after the original transaction.</li>\n<li>If any domain event handlers fail, we risk failing the request even though the initial transaction was completed successfully.</li>\n</ol>\n<p>You can make this process more reliable by using an <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox.</strong></a></p>\n<pre><code class=\"language-csharp\">internal sealed class PublishDomainEventsInterceptor : SaveChangesInterceptor\n{\n    private readonly IPublisher _publisher;\n\n    public PublishDomainEventsInterceptor(IPublisher publisher)\n    {\n        _publisher = publisher;\n    }\n\n    public override async ValueTask&lt;int&gt; SavedChangesAsync(\n        SaveChangesCompletedEventData eventData,\n        int result,\n        CancellationToken cancellationToken = default)\n    {\n        if (eventData.Context is not null)\n        {\n            await PublishDomainEventsAsync(eventData.Context);\n        }\n\n        return result;\n    }\n\n    private async Task PublishDomainEventsAsync(DbContext context)\n    {\n        var domainEvents = context\n            .ChangeTracker\n            .Entries&lt;Entity&gt;()\n            .Select(entry =&gt; entry.Entity)\n            .SelectMany(entity =&gt;\n            {\n                List&lt;IDomainEvent&gt; domainEvents = entity.DomainEvents;\n\n                entity.ClearDomainEvents();\n\n                return domainEvents;\n            })\n            .ToList();\n\n        foreach (IDomainEvent domainEvent in domainEvents)\n        {\n            await _publisher.Publish(domainEvent);\n        }\n    }\n}\n</code></pre>\n<h2>Store Outbox Messages With EF Interceptors</h2>\n<p>Instead of <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>publishing domain events</strong></a> as part of the EF transaction, you can convert them to Outbox messages.</p>\n<p>Here's an <code>InsertOutboxMessagesInterceptor</code> that does precisely this.</p>\n<p>It overrides the <code>SavingChangesAsync</code> method.\nWhich means it runs inside the current EF transaction before saving changes.</p>\n<p>The <code>InsertOutboxMessagesInterceptor</code> converts any domain events into an <code>OutboxMessage</code> and adds it to the respective <code>DbSet&lt;OutboxMessage&gt;</code>.\nThis means they will be saved to the database with any existing changes inside the same transaction.</p>\n<p>This is an atomic operation.</p>\n<p>Either everything succeeds or everything fails.</p>\n<p>There's no in-between state like in the <code>PublishDomainEventsInterceptor</code>.</p>\n<p>You can then create a background worker that will process the Outbox messages.</p>\n<p>And this is how you implement the <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox pattern</strong></a> with EF Core.</p>\n<pre><code class=\"language-csharp\">using Newtonsoft.Json;\n\npublic sealed class InsertOutboxMessagesInterceptor : SaveChangesInterceptor\n{\n    private static readonly JsonSerializerSettings Serializer = new()\n    {\n        TypeNameHandling = TypeNameHandling.All\n    };\n\n    public override ValueTask&lt;InterceptionResult&lt;int&gt;&gt; SavingChangesAsync(\n        DbContextEventData eventData,\n        InterceptionResult&lt;int&gt; result,\n        CancellationToken cancellationToken = default)\n    {\n        if (eventData.Context is not null)\n        {\n            InsertOutboxMessages(eventData.Context);\n        }\n\n        return base.SavingChangesAsync(eventData, result, cancellationToken);\n    }\n\n    private static void InsertOutboxMessages(DbContext context)\n    {\n        context\n            .ChangeTracker\n            .Entries&lt;Entity&gt;()\n            .Select(entry =&gt; entry.Entity)\n            .SelectMany(entity =&gt;\n            {\n                List&lt;IDomainEvent&gt; domainEvents = entity.DomainEvents;\n\n                entity.ClearDomainEvents();\n\n                return domainEvents;\n            })\n            .Select(domainEvent =&gt; new OutboxMessage\n            {\n                Id = domainEvent.Id,\n                OccurredOnUtc = domainEvent.OccurredOnUtc,\n                Type = domainEvent.GetType().Name,\n                Content = JsonConvert.SerializeObject(domainEvent, Serializer)\n            })\n            .ToList();\n\n        context.Set&lt;OutboxMessage&gt;().AddRange(outboxMessages);\n    }\n}\n</code></pre>\n<h2>Configuring EF Interceptors Using Dependency Injection</h2>\n<p>EF interceptors should be lightweight and stateless.\nYou can add them to the <code>DbContext</code> by calling <code>AddInterceptors</code> and passing in the interceptor instances.</p>\n<p>I like to configure the interceptors with Dependency Injection for two reasons:</p>\n<ul>\n<li>It allows me also to use DI in the interceptors (be mindful that they are singletons)</li>\n<li>To simplify adding the interceptors to the <code>DbContext</code> using <code>AddDbContext</code></li>\n</ul>\n<p>Here's how you can configure the <code>UpdateAuditableInterceptor</code> and <code>InsertOutboxMessagesInterceptor</code> with the <code>ApplicationDbContext</code>:</p>\n<pre><code class=\"language-csharp\">services.AddSingleton&lt;UpdateAuditableInterceptor&gt;();\nservices.AddSingleton&lt;InsertOutboxMessagesInterceptor&gt;();\n\nservices.AddDbContext&lt;IApplicationDbContext, ApplicationDbContext&gt;(\n    (sp, options) =&gt; options\n        .UseSqlServer(connectionString)\n        .AddInterceptors(\n            sp.GetRequiredService&lt;UpdateAuditableInterceptor&gt;(),\n            sp.GetRequiredService&lt;InsertOutboxMessagesInterceptor&gt;()));\n</code></pre>\n<h2>Closing Thoughts</h2>\n<p>Interceptors allow you to do almost anything with an EF Core operation.\nBut with great power comes great responsibility.\nYou should be mindful that interceptors have an impact on performance.\nCalls to external services or handling events will slow down the operation.</p>\n<p>Remember, you don't necessarily have to use EF interceptors.\nYou can achieve the same behavior by overriding the <code>SaveChangesAsync</code> method on the <code>DbContext</code> and adding your custom logic there.</p>\n<p>I showed you a few practical use cases for EF interceptors in this week's issue.</p>\n<p>But, if you want to see more examples, I have a few videos about:</p>\n<ul>\n<li><a href=\"https://youtu.be/mAlO3OuoQvo\"><strong>Auditing logging</strong></a></li>\n<li><a href=\"https://youtu.be/AHzWJ_SMqLo\"><strong>Publishing domain events</strong></a></li>\n<li><a href=\"https://youtu.be/XALvnX7MPeo\"><strong>Implementing the Outbox pattern</strong></a></li>\n</ul>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors",
            "title": "How To Use EF Core Interceptors",
            "summary": "EF Core is my favorite ORM for .NET applications, but many of its fantastic features go unnoticed.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_064.png",
            "date_modified": "2023-11-18T00:00:00.000Z",
            "date_published": "2023-11-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore",
            "content_html": "<p>There are two common ways to generate PDF documents in .NET.\nYou can compose the document in code with a library like QuestPDF and its fluent API.\nOr you can build an HTML template, for example an ASP.NET Core Razor view, and convert it to PDF with a library like IronPDF.</p>\n<p>Reporting is essential for business applications like e-commerce, shipping, fintech, etc.</p>\n<p>One of the most popular document formats for reporting purposes is <a href=\"https://en.wikipedia.org/wiki/PDF\">PDF.</a></p>\n<p>PDF stands for Portable Document Format.\nIt's a file format to present documents (including text formatting and images) independently of application software, hardware, and operating systems.</p>\n<p>Some common problems .NET developers will face when working with PDF files:</p>\n<ul>\n<li>Creating dynamic PDF documents</li>\n<li>Designing a consistent page layout</li>\n<li>Customizing fonts on printed documents</li>\n</ul>\n<p>Today I want to show you a few interesting ways to generate PDF files in .NET.</p>\n<h2>Creating PDF Files With QuestPDF</h2>\n<p><a href=\"https://www.questpdf.com/\">QuestPDF</a> is an open-source .NET library for generating PDF documents.\nIt exposes a fluent API you can use to compose together many simple elements to create complex documents.\nUnlike other libraries, it does not rely on HTML-to-PDF conversion.</p>\n<p>Let's install the QuestPDF NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package QuestPDF\n</code></pre>\n<p>Here's how you can generate a simplified invoice with QuestPDF:</p>\n<pre><code class=\"language-csharp\">using QuestPDF.Fluent;\nusing QuestPDF.Helpers;\nusing QuestPDF.Infrastructure;\n\nDocument.Create(container =&gt;\n{\n    container.Page(page =&gt;\n    {\n        page.Margin(50);\n        page.Size(PageSizes.A4);\n        page.PageColor(Colors.White);\n        page.DefaultTextStyle(x =&gt; x.FontSize(16));\n\n        page.Header()\n            .AlignCenter()\n            .Text(&quot;Invoice #: 2023-77&quot;)\n            .SemiBold().FontSize(24).FontColor(Colors.Grey.Darken4);\n\n        page.Content()\n            .Table(table =&gt;\n            {\n                table.ColumnsDefinition(columns =&gt;\n                {\n                    columns.ConstantColumn(20);\n                    columns.RelativeColumn();\n                    columns.RelativeColumn();\n                });\n\n                table.Header(header =&gt;\n                {\n                    header.Cell().Text(&quot;#&quot;);\n                    header.Cell().Text(&quot;Product&quot;);\n                    header.Cell().AlignRight().Text(&quot;Price&quot;);\n                });\n\n                foreach (var lineItem in lineItems)\n                {\n                    table.Cell().Text(lineItem.Index.ToString());\n                    table.Cell().Text(lineItem.Name);\n                    table.Cell().Text($&quot;${lineItem.Price}&quot;);\n                }\n            });\n    });\n})\n.GeneratePdf(&quot;invoice.pdf&quot;);;\n</code></pre>\n<p>What I like about QuestPDF:</p>\n<ul>\n<li>Fluent API</li>\n<li>Easy to use</li>\n<li>Good <a href=\"https://www.questpdf.com/quick-start.html\">documentation</a></li>\n</ul>\n<p>What I don't like about QuestPDF:</p>\n<ul>\n<li>Having to write a lot of code to create documents</li>\n<li>Limited scope of features</li>\n<li>No HTML-to-PDF support</li>\n</ul>\n<p><strong>Licensing</strong></p>\n<p>QuestPDF is free for small companies and development use.\nThere's also a commercial license for larger companies.\nYou can check out the licensing details <a href=\"https://www.questpdf.com/license/\">here.</a></p>\n<h2>HTML to PDF Conversion With IronPDF</h2>\n<p>The more common approach for generating PDF files is using an HTML template.</p>\n<p>My favorite library that supports this is <a href=\"https://ironpdf.com/\">IronPDF.</a></p>\n<p>IronPDF is a C# PDF library that allows for fast and efficient manipulation of PDF files.\nIt also has many valuable features, like <a href=\"https://ironpdf.com/how-to/pdfa/\">exporting to PDF/A format</a>\nand <a href=\"https://ironpdf.com/how-to/signing/\">digitally signing PDF documents.</a></p>\n<p>But what's the idea behind using an HTML template?</p>\n<p>First of all, you have more control over formatting the document.\nYou can use CSS to style the HTML markup, which will be applied when exporting to a PDF document.</p>\n<p>An interesting implementation approach is using <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-7.0\">ASP.NET Core MVC views</a>\nand the <a href=\"https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0\">Razor syntax.</a>\nYou can pass an object to the view at runtime to render dynamic HTML content.</p>\n<p>I've used this approach with MVC views on a few projects with excellent results.</p>\n<p>Let's start by installing the IronPDF NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package IronPdf\n</code></pre>\n<p>I'm using a strongly typed Razor view to define my markup.\nThe <code>InoviceViewModel</code> class is the model, and it's used to create dynamic content.</p>\n<pre><code class=\"language-tsx\">@model ViewModels.InoviceViewModel\n\n&lt;div&gt;Invoice number: @Model.InvoiceNumber&lt;/div&gt;\n&lt;div&gt;Invoice date: @Model.InvoiceDate&lt;/div&gt;\n&lt;br/&gt;\n&lt;span&gt;Line items:&lt;/span&gt;\n&lt;ul&gt;\n    @foreach(var lineItem in Model.LineItems)\n    {\n        &lt;li&gt;@lineItem.Name | @lineItem.Price&lt;/li&gt;\n    }\n&lt;/ul&gt;\n</code></pre>\n<p>Now, you need to use the IronPDF <code>ChromePdfRenderer</code> to convert the HTML to a PDF document.</p>\n<pre><code class=\"language-csharp\">var html = ConvertRazorViewToHtml(invoice);\n\nvar renderer = new ChromePdfRenderer();\n\nvar pdf = renderer.RenderHtmlAsPdf(html);\n\npdf.SaveAs($&quot;invoice-{invoice.InvoiceNumber}.pdf&quot;);\n</code></pre>\n<p>It really is that simple.</p>\n<p><strong>Licensing</strong></p>\n<p>IronPDF is free for development use and has multiple pricing tiers for commercial use that you can check out <a href=\"https://ironpdf.com/licensing/\">here.</a></p>\n<h2>Merging Multiple PDF Files</h2>\n<p>Another common requirement I've seen is merging multiple PDF files.\nFor example, you could implement a feature to merge the monthly receipts for the accounting department.</p>\n<p>You can use the <code>PdfDocument.Merge</code> method to implement this.\nIt accepts a <code>PdfDocument</code> collection as the argument.\nYou'll first have to load the PDF documents into memory before merging them.</p>\n<p>Here's an example:</p>\n<pre><code class=\"language-csharp\">var pdfs = new List&lt;PdfDocument&gt;();\n\npdfs.Add(PdfDocument.FromFile(&quot;google-invoice.pdf&quot;));\npdfs.Add(PdfDocument.FromFile(&quot;google-ads-invoice.pdf&quot;));\npdfs.Add(PdfDocument.FromFile(&quot;converkit-invoice.pdf&quot;));\n\nPdfDocument mergedPdfDocument = PdfDocument.Merge(pdfs);\n\nmergedPdfDocument.SaveAs(&quot;merged-invoices.pdf&quot;);\n</code></pre>\n<h2>Exporting PDF Files From an API</h2>\n<p>It's pretty straightforward to return a PDF file from an API endpoint in ASP.NET Core.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal APIs</strong></a> have the <code>Results.File</code> method accepting either a file path, stream, or byte array.\nYou also need to specify the content type and an optional file name.\nThe <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\">MIME type</a>\nfor PDF files is <code>application/pdf</code>.</p>\n<p>Here's how you can return a PDF file from a byte array:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;newsletter/download&quot;, () =&gt;\n{\n    var renderer = new ChromePdfRenderer();\n\n    var pdf = renderer.RenderHtmlAsPdf(&quot;&lt;h1&gt;The .NET Weekly&lt;/h1&gt;&quot;);\n\n    return Results.File(pdf.BinaryData, &quot;application/pdf&quot;, &quot;newsletter.pdf&quot;);\n});\n</code></pre>\n<h2>Takeaway</h2>\n<p>Choosing which PDF library you will use in .NET is an important consideration to make.\nAnd while pricing is a significant factor, the features you want to use will also dictate your choice.</p>\n<p>QuestPDF is an excellent choice if you're looking for a (mostly) free option with rich features.\nThe library is constantly improved, and new features are being added.\nHowever, it doesn't support HTML-to-PDF conversion and modifying existing documents.</p>\n<p>IronPDF is the library I've used most often on commercial projects.\nIt has fantastic features for working with PDF files, with many customization options.\nThe HTML-to-PDF conversion works like a charm.</p>\n<p>The hardest part is picking the right tool for the job.</p>\n<p>So I hope this is helpful.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore",
            "title": "How To Easily Create PDF Documents in ASP.NET Core",
            "summary": "Reporting is essential for business applications like e-commerce, shipping, fintech, etc. One of the most popular document formats for reporting purposes is…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_063.png",
            "date_modified": "2023-11-11T00:00:00.000Z",
            "date_published": "2023-11-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/vertical-slice-architecture",
            "content_html": "<p><strong>Vertical Slice Architecture</strong> organizes a system around features instead of technical layers.\nAll the files for a single use case live in one folder.\nThis minimizes coupling between unrelated features and maximizes coupling inside a single feature.</p>\n<p>Layered architectures are the foundation of many software systems.\nHowever, layered architectures organize the system around technical layers.\nAnd the cohesion between layers is low.</p>\n<p>What if you wanted to organize the system around features instead?</p>\n<p>Minimize coupling between unrelated features and maximize coupling in a single feature.</p>\n<p>Today I want to talk about <strong>Vertical Slice Architecture</strong>, which does precisely that.</p>\n<h2>The Problem With Layered Architectures</h2>\n<p>Layered architectures organize the software system into layers or tiers.\nEach of the layers is typically one project in your solution.\nSome of the popular implementations are N-tier architecture or <a href=\"https://milanjovanovic.tech/blog/clean-architecture-dotnet\"><strong>Clean architecture</strong></a>.</p>\n<p>Layered architectures focus on separating the concerns of the various components.\nThis makes it easier to understand and maintain the software system.\nAnd there are many benefits of <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\"><strong>structured software design,</strong></a>\nsuch as maintainability, flexibility, and loose coupling.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_062/clean_architecture.png\" alt=\"Clean Architecture mapping entities, use cases, API endpoints, and external services to their respective layers\">\n<p>However, layered architectures also impose constraints or rigid rules on your system.\nThe direction of dependencies between layers is pre-determined.</p>\n<p>For example, in Clean Architecture:</p>\n<ul>\n<li>Domain should have no dependencies</li>\n<li>Application layer can reference the Domain</li>\n<li>Infrastructure can reference both Application and Domain</li>\n<li>Presentation can reference both Application and Domain</li>\n</ul>\n<p>You end up having high coupling inside a layer and low coupling between layers.\nThis doesn't mean layered architectures are bad.\nBut it does mean you will have many abstractions between individual layers.\nAnd more abstractions mean increased complexity because there are more components to maintain.</p>\n<h2>What is Vertical Slice Architecture?</h2>\n<p>I first heard about <a href=\"https://www.jimmybogard.com/vertical-slice-architecture\">Vertical Slice Architecture</a> from Jimmy Bogard.\nHe's also the creator of some popular open-source libraries like <a href=\"https://github.com/jbogard/MediatR\">MediatR</a> and <a href=\"https://github.com/AutoMapper/AutoMapper\">Automapper.</a></p>\n<p>Vertical Slice Architecture was born from the pain of working with layered architectures.\nThey force you to make changes in many different layers to implement a feature.</p>\n<p>Let's imagine what adding a new feature looks like in a layered architecture:</p>\n<ul>\n<li>Updating the domain model</li>\n<li>Modifying validation logic</li>\n<li>Creating a use case with MediatR</li>\n<li>Exposing an API endpoint from a controller</li>\n</ul>\n<p>The cohesion is low because you are creating many files in different layers.</p>\n<p>Vertical slices take a different approach:</p>\n<blockquote>\n<p>Minimize coupling between slices, and maximize coupling in a slice.</p>\n</blockquote>\n<p>Here's how you can visualize vertical slices:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_062/vertical_slice_architecture.png\" alt=\"Vertical slice architecture with each feature crossing Presentation, Application, Domain, and Infrastructure layers\">\n<p>All the files for a single use case are grouped inside one folder.\nSo, the cohesion for a single use case is very high.\nThis simplifies the development experience.\nIt's easy to find all the relevant components for each feature since they are close together.</p>\n<h2>Implementing Vertical Slices</h2>\n<p>If you're building an API, the system already breaks down into commands (POST/PUT/DELETE) and queries (GET).\nBy splitting the requests into commands and queries, you're getting the benefits of the <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\"><strong>CQRS pattern.</strong></a></p>\n<p>Vertical slices narrowly focus on a single feature.\nThis allows you to treat each use case separately and tailor the implementation to the specific requirements.\nOne vertical slice can use EF Core to implement a GET request.\nAnother vertical slice can use <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a> with raw SQL queries.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_062/vertical_slices.png\" alt=\"Four activity API vertical slices using EF Core, Dapper, a rich domain model, and ExecuteDeleteAsync\">\n<p>Another benefit of implementing vertical slices like this is:</p>\n<blockquote>\n<p>New features only add code, you're not changing shared code and worrying about side effects.</p>\n</blockquote>\n<p>However, vertical slices have their own set of challenges.\nBecause you are implementing much of the business logic inside a single use case, you need to be able to spot code smells.\nAs the use case grows, it can end up doing too much.\nYou will have to refactor the code by <a href=\"https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model\"><strong>pushing logic to the domain.</strong></a></p>\n<h2>Solution Structure With REPR Pattern</h2>\n<p>Layered architectures, such as Clean architecture, organize the solution across layers.\nThis results in a <a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure\"><strong>folder structure grouped by technical concerns.</strong></a></p>\n<p>Vertical slice architecture, on the other hand, organizes the code around features or use cases.</p>\n<p>An interesting approach to structuring APIs around features is using the <a href=\"https://deviq.com/design-patterns/repr-design-pattern\">REPR pattern.</a>\nIt stands for Request-EndPoint-Response.\nThis aligns perfectly with the idea of vertical slices.\nYou can achieve this with the MediatR library, for example.</p>\n<p>The REPR pattern defines that web API endpoints should have three components:</p>\n<ul>\n<li>Request</li>\n<li>Endpoint</li>\n<li>Response</li>\n</ul>\n<p>Here's an example solution structure in .NET.\nYou'll notice the <code>Features</code> folder, which contains the vertical slices.\nEach vertical slice implements one API request (or use case).</p>\n<pre><code>🔗 RunTracker.API\n|__ 📁 Database\n|__ 📁 Entities\n    |__ #️⃣ Activity.cs\n    |__ #️⃣ Workout.cs\n    |__ #️⃣ ...\n|__ 📁 Features\n    |__ 📁 Activities\n        |__ 📁 GetActivity\n            |__ #️⃣ ActivityResponse.cs\n            |__ #️⃣ GetActivityEndpoint.cs\n            |__ #️⃣ GetActivityQuery.cs\n            |__ #️⃣ GetActivityQueryHandler.cs\n        |__ 📁 CreateActivity\n            |__ #️⃣ CreateActivity.cs\n                |__ #️⃣ CreateActivity.Command.cs\n                |__ #️⃣ CreateActivity.Endpoint.cs\n                |__ #️⃣ CreateActivity.Handler.cs\n                |__ #️⃣ CreateActivity.Validator.cs\n    |__ 📁 Workouts\n    |__ 📁 ...\n|__ 📁 Middleware\n|__ 📄 appsettings.json\n|__ 📄 appsettings.Development.json\n|__ #️⃣ Program.cs\n</code></pre>\n<p>A few more libraries for implementing the <a href=\"https://milanjovanovic.tech/blog/repr-pattern-aspnetcore\"><strong>REPR pattern</strong></a>:</p>\n<ul>\n<li><a href=\"https://github.com/FastEndpoints/FastEndpoints\">FastEndpoints</a></li>\n<li><a href=\"https://github.com/ardalis/ApiEndpoints\">ApiEndpoints</a></li>\n</ul>\n<h2>Next Steps</h2>\n<p>Some of you may not like the idea of grouping all the files related to a feature in a single folder.</p>\n<p>However, there's a lot of value in grouping by features in general.\nYou don't have to implement vertical slices.\nBut you can apply this concept to your domain by grouping files around aggregates, for example.\nThis is the approach I show in <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture.</strong></a></p>\n<p>I made a video about <a href=\"https://youtu.be/msjnfdeDCmo\"><strong>Vertical Slice Architecture,</strong></a>\nshowing how to implement the concepts discussed in today's issue. Check it out <a href=\"https://youtu.be/msjnfdeDCmo\"><strong>here.</strong></a></p>\n<p>Thanks for reading, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/vertical-slice-architecture",
            "title": "Vertical Slice Architecture",
            "summary": "Layered architectures are the foundation of many software systems, but they organize the system around technical layers, and the cohesion between layers is low.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_062.png",
            "date_modified": "2023-11-04T00:00:00.000Z",
            "date_published": "2023-11-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern",
            "content_html": "<p>The <strong>Result pattern</strong> is a functional approach to error handling in .NET.\nInstead of throwing exceptions, a method returns a <code>Result</code> object that exposes <code>IsSuccess</code>, <code>IsFailure</code>, and an <code>Error</code> describing what went wrong.\nThe return type makes it explicit that the method can fail.</p>\n<p>How should you handle errors in your code?</p>\n<p>This has been a topic of many discussions, and I want to share my opinion.</p>\n<p>One school of thought suggests using exceptions for flow control.\nThis is not a good approach because it makes the code harder to reason about.\nThe caller must know the implementation details and which exceptions to handle.</p>\n<p>Exceptions are for exceptional situations.</p>\n<p>Today, I want to show you how to implement error handling using the <strong>Result pattern.</strong></p>\n<p>It's a functional approach to error handling, making your code more expressive.</p>\n<h2>Exceptions For Flow Control</h2>\n<p>Using exceptions for flow control is an approach to implement the <strong>fail-fast</strong> principle.</p>\n<p>As soon as you encounter an error in the code, you throw an exception —\neffectively terminating the method, and making the caller responsible for handling the exception.</p>\n<p>The problem is the caller must know which exceptions to handle.\nAnd this isn't obvious from the method signature alone.</p>\n<p>Another common use case is throwing exceptions for validation errors.</p>\n<p>Here's an example in the <code>FollowerService</code>:</p>\n<pre><code class=\"language-csharp\">public sealed class FollowerService\n{\n    private readonly IFollowerRepository _followerRepository;\n\n    public FollowerService(IFollowerRepository followerRepository)\n    {\n        _followerRepository = followerRepository;\n    }\n\n    public async Task StartFollowingAsync(\n        User user,\n        User followed,\n        DateTime createdOnUtc,\n        CancellationToken cancellationToken = default)\n    {\n        if (user.Id == followed.Id)\n        {\n            throw new DomainException(&quot;Can't follow yourself&quot;);\n        }\n\n        if (!followed.HasPublicProfile)\n        {\n            throw new DomainException(&quot;Can't follow non-public profile&quot;);\n        }\n\n        if (await _followerRepository.IsAlreadyFollowingAsync(\n                user.Id,\n                followed.Id,\n                cancellationToken))\n        {\n            throw new DomainException(&quot;Already following&quot;);\n        }\n\n        var follower = Follower.Create(user.Id, followed.Id, createdOnUtc);\n\n        _followerRepository.Insert(follower);\n    }\n}\n</code></pre>\n<h2>Use Exceptions for Exceptional Situations</h2>\n<p>A rule of thumb I follow is to use exceptions for exceptional situations.\nSince you already expect potential errors, why not make it explicit?</p>\n<p>You can group all application errors into two groups:</p>\n<ul>\n<li>Errors you know how to handle</li>\n<li>Errors you don't know how to handle</li>\n</ul>\n<p>Exceptions are an excellent solution for the errors you don't know how to handle.\nAnd you should catch and handle them at the lowest level possible.</p>\n<p>What about the errors you know how to handle?</p>\n<p>You can handle them in a functional way with the <strong>Result pattern.</strong>\nIt's explicit and clearly expresses the intent that the method can fail.\nThe drawback is the caller has to manually check if the operation failed.</p>\n<h2>Expressing Errors Using the Result Pattern</h2>\n<p>The first thing you will need is an <code>Error</code> class to represent application errors.</p>\n<ul>\n<li><code>Code</code> - unique name for the error in the application</li>\n<li><code>Description</code> - contains developer-friendly details about the error</li>\n</ul>\n<pre><code class=\"language-csharp\">public sealed record Error(string Code, string Description)\n{\n    public static readonly Error None = new(string.Empty, string.Empty);\n}\n</code></pre>\n<p>Then, you can implement the <code>Result</code> class using the <code>Error</code> to describe the failure.\nThis implementation is very bare-bones, and you could add many more features.\nIn most cases, you also need a generic <code>Result&lt;T&gt;</code> class, which will wrap a value inside.</p>\n<p>Here's what the <code>Result</code> class looks like:</p>\n<pre><code class=\"language-csharp\">public class Result\n{\n    private Result(bool isSuccess, Error error)\n    {\n        if (isSuccess &amp;&amp; error != Error.None ||\n            !isSuccess &amp;&amp; error == Error.None)\n        {\n            throw new ArgumentException(&quot;Invalid error&quot;, nameof(error));\n        }\n\n        IsSuccess = isSuccess;\n        Error = error;\n    }\n\n    public bool IsSuccess { get; }\n\n    public bool IsFailure =&gt; !IsSuccess;\n\n    public Error Error { get; }\n\n    public static Result Success() =&gt; new(true, Error.None);\n\n    public static Result Failure(Error error) =&gt; new(false, error);\n}\n</code></pre>\n<p>The only way to create a <code>Result</code> instance is by using static methods:</p>\n<ul>\n<li><code>Success</code> - creates a success result</li>\n<li><code>Failure</code> - creates a failure result with the specified <code>Error</code></li>\n</ul>\n<p>If you want to avoid building your own <code>Result</code> class, take a look at the <a href=\"https://github.com/altmann/FluentResults\">FluentResults</a> library.</p>\n<h2>Applying the Result Pattern</h2>\n<p>Now that we have the <code>Result</code> class let's see how to apply it in practice.</p>\n<p>Here's a refactored version of the <code>FollowerService</code>.\nNotice a few things:</p>\n<ul>\n<li>No more throwing exceptions</li>\n<li>The <code>Result</code> return type is explicit</li>\n<li>It's clear which errors the method returns</li>\n</ul>\n<p>Another benefit of error handling using the <strong>Result pattern</strong> is that it's easier to test.</p>\n<pre><code class=\"language-csharp\">public sealed class FollowerService\n{\n    private readonly IFollowerRepository _followerRepository;\n\n    public FollowerService(IFollowerRepository followerRepository)\n    {\n        _followerRepository = followerRepository;\n    }\n\n    public async Task&lt;Result&gt; StartFollowingAsync(\n        User user,\n        User followed,\n        DateTime utcNow,\n        CancellationToken cancellationToken = default)\n    {\n        if (user.Id == followed.Id)\n        {\n            return Result.Failure(FollowerErrors.SameUser);\n        }\n\n        if (!followed.HasPublicProfile)\n        {\n            return Result.Failure(FollowerErrors.NonPublicProfile);\n        }\n\n        if (await _followerRepository.IsAlreadyFollowingAsync(\n                user.Id,\n                followed.Id,\n                cancellationToken))\n        {\n            return Result.Failure(FollowerErrors.AlreadyFollowing);\n        }\n\n        var follower = Follower.Create(user.Id, followed.Id, utcNow);\n\n        _followerRepository.Insert(follower);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<h2>Documenting Application Errors</h2>\n<p>You can use the <code>Error</code> class to document all possible errors in your application.</p>\n<p>One approach is to create a static class called <code>Errors</code>.\nIt will have nested classes inside containing the specific errors.\nThe usage would look like <code>Errors.Followers.NonPublicProfile</code>.</p>\n<p>However, the approach I like to use is to create a specific class containing the errors.</p>\n<p>Here's the <code>FollowerErrors</code> class documenting the possible errors for the <code>Follower</code> entity:</p>\n<pre><code class=\"language-csharp\">public static class FollowerErrors\n{\n    public static readonly Error SameUser = new Error(\n        &quot;Followers.SameUser&quot;, &quot;Can't follow yourself&quot;);\n\n    public static readonly Error NonPublicProfile = new Error(\n        &quot;Followers.NonPublicProfile&quot;, &quot;Can't follow non-public profiles&quot;);\n\n    public static readonly Error AlreadyFollowing = new Error(\n        &quot;Followers.AlreadyFollowing&quot;, &quot;Already following&quot;);\n}\n</code></pre>\n<p>Instead of static fields, you can also use static methods returning an error.\nYou would call this method with a concrete argument to get an <code>Error</code> instance.</p>\n<pre><code class=\"language-csharp\">public static class FollowerErrors\n{\n    public static Error NotFound(Guid id) =&gt; new Error(\n        &quot;Followers.NotFound&quot;, $&quot;The follower with Id '{id}' was not found&quot;);\n}\n</code></pre>\n<h2>Converting Results Into API Responses</h2>\n<p>The <code>Result</code> object will eventually reach the <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> (or controller) endpoint in ASP.NET Core.\nMinimal APIs return an <code>IResult</code> response, and controllers return an <code>IActionResult</code> response.\nRegardless, you must convert the <code>Result</code> instance into a valid API response.</p>\n<p>The straightforward approach is checking the <code>Result</code> state and returning an HTTP response.\nHere's an example where we check the <code>Result.IsFailure</code> flag:</p>\n<pre><code class=\"language-csharp\">app.MapPost(\n    &quot;users/{userId}/follow/{followedId}&quot;,\n    (Guid userId, Guid followedId, FollowerService followerService) =&gt;\n    {\n        var result = await followerService.StartFollowingAsync(\n            userId,\n            followedId,\n            DateTime.UtcNow);\n\n        if (result.IsFailure)\n        {\n            return Results.BadRequest(result.Error);\n        }\n\n        return Results.NoContent();\n    });\n</code></pre>\n<p>However, this is an excellent opportunity for a more functional approach.\nYou can implement the <code>Match</code> extension method to provide a callback for each <code>Result</code> state.\nThe <code>Match</code> method will execute the respective callback and return the result.</p>\n<p>Here's the implementation of <code>Match</code>:</p>\n<pre><code class=\"language-csharp\">public static class ResultExtensions\n{\n    public static T Match&lt;T&gt;(\n        this Result result,\n        Func&lt;T&gt; onSuccess,\n        Func&lt;Error, T&gt; onFailure)\n    {\n        return result.IsSuccess ? onSuccess() : onFailure(result.Error);\n    }\n}\n</code></pre>\n<p>And this is how you would use the <code>Match</code> method in a Minimal API endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapPost(\n    &quot;users/{userId}/follow/{followedId}&quot;,\n    (Guid userId, Guid followedId, FollowerService followerService) =&gt;\n    {\n        var result = await followerService.StartFollowingAsync(\n            userId,\n            followedId,\n            DateTime.UtcNow);\n\n        return result.Match(\n            onSuccess: () =&gt; Results.NoContent(),\n            onFailure: error =&gt; Results.BadRequest(error));\n    });\n</code></pre>\n<p>Much more concise. Don't you think so?</p>\n<h2>Summary</h2>\n<p>If you take one thing with you from this week's issue, it should be this: exceptions are for exceptional situations.\nMoreover, you should only use exceptions for errors you don't know how to handle.\nIn all other cases, expressing the error clearly with the <strong>Result pattern</strong> is more valuable.</p>\n<p>Using the <code>Result</code> class allows you to:</p>\n<ul>\n<li>Express the intent that a method <em>could</em> fail</li>\n<li>Encapsulate an application error inside</li>\n<li>Provide a functional way to handle errors</li>\n</ul>\n<p>Additionally, you can document all application errors with the <code>Error</code> class.\nThis is helpful for developers to know which errors they need to handle.</p>\n<p>You can even convert this to actual <em>documentation</em>.\nFor example, I wrote a simple program that scans the project for all <code>Error</code> fields.\nIt then converts this into a table format and uploads it to a Confluence page.</p>\n<p>So I encourage you to try the <strong>Result pattern</strong> and see how it can improve your code.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern",
            "title": "Functional Error Handling in .NET With the Result Pattern",
            "summary": "How should you handle errors in your code? Exceptions are for exceptional situations, and using them for flow control makes the code harder to reason about.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_061.png",
            "date_modified": "2023-10-28T00:00:00.000Z",
            "date_published": "2023-10-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr",
            "content_html": "<p>Command Query Responsibility Segregation (CQRS) gives commands and queries their own models, so you can optimize writes and reads independently.\nThe separation can be logical inside one database or physical across two.\nWith MediatR you extend <code>IRequest</code> into custom <code>ICommand</code> and <code>IQuery</code> abstractions, and <code>ISender</code> routes each one to its handler.</p>\n<p>Today I want to show you how to use the <strong>CQRS</strong> pattern to build fast and scalable applications.</p>\n<p>The CQRS pattern separates the writes and reads in the application.</p>\n<p>This separation can be logical or physical and has many benefits:</p>\n<ul>\n<li>Complexity management</li>\n<li>Improved performance</li>\n<li>Scalability</li>\n<li>Flexibility</li>\n<li>Security</li>\n</ul>\n<p>I'm also going to show you how to implement CQRS in your application using MediatR.</p>\n<p>But first, we have to understand what CQRS is.</p>\n<h2>What Exactly is CQRS?</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs\">CQRS</a> stands for <strong>Command Query Responsibility Segregation</strong>.\nThe CQRS pattern uses separate models for reading and updating data.\nThe benefits of using CQRS are complexity management, improved performance, scalability, and security.</p>\n<p>The standard approach for working with a database is using the same model to query and update data.\nThis is simple and works great for most CRUD operations.\nHowever, in more complex applications, it becomes difficult to maintain.\nOn the write side, you could have complex business logic and validation in the model.\nOn the read side, you may need to perform many different queries.</p>\n<p>Also, consider how we create the data model.\nApplying SQL data modeling best practices will give you a normalized database.\nThis is generally fine, but it's optimized for writing.</p>\n<p>Having separate models for commands and queries allows you to scale them independently.\nThe separation could be logical while using the same database.\nYou could split the subsystems for commands and queries into separate services.\nAnd you can even have multiple databases optimized for writing or reading data.</p>\n<h2>How Is It Different From CQS?</h2>\n<p><a href=\"https://en.wikipedia.org/wiki/Command%E2%80%93query_separation\">CQS</a> stands for <strong>Command Query Separation</strong>.\nIt's a term coined by Bertrand Meyer in his book <a href=\"https://en.wikipedia.org/wiki/Object-Oriented_Software_Construction\">Object-Oriented Software Construction.</a></p>\n<p>The basic premise of CQS is splitting an object's methods into <strong>Commands</strong> and <strong>Queries</strong>.</p>\n<ul>\n<li><strong>Commands</strong>: Change the state of a system but don't return a value</li>\n<li><strong>Queries</strong>: Return a value and don't change the state of the system (no side effects)</li>\n</ul>\n<p>This doesn't mean a command can never return a value.\nA typical example is popping a value from a stack.\nIt returns a value and changes the state of the system.\nBut the intent is what matters here.</p>\n<p>CQS is a <em>principle.</em>\nYou can follow this principle if it makes sense, but be pragmatic.</p>\n<p>CQRS is the evolution of CQS.\nCQRS works on the architectural level.\nAt the same time, CQS works on the method (or class) level.</p>\n<h2>Many Flavors of CQRS</h2>\n<p>Here's a high-level overview of a CQRS system using multiple databases.\nCommands update the write database.\nThen, you need to synchronize the updates with the read database.\nThis introduces eventual consistency to CQRS systems.</p>\n<p>Eventual consistency significantly increases the complexity of your application.\nYou must consider what happens if the synchronization process fails, and have a fault tolerance strategy.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_060/cqrs.png\" alt=\"Diagram of a system using CQRS with two databases.\">\n<p>There are many flavors of this approach:</p>\n<ul>\n<li>SQL database on the write side and NoSQL database (for example, <a href=\"https://ravendb.net/\">RavenDB</a>) on the read side</li>\n<li>Event sourcing on the write side and NoSQL database on the read side</li>\n<li>Using Redis or some other distributed cache on the read side</li>\n</ul>\n<p>Separating the models for updating and reading data allows you to choose the best database for your requirements.</p>\n<h2>Logical CQRS Architecture</h2>\n<p>How do you apply the CQRS pattern to your system?\nI prefer using <a href=\"https://github.com/jbogard/MediatR\">MediatR.</a></p>\n<p>MediatR implements the <a href=\"https://refactoring.guru/design-patterns/mediator\">mediator pattern</a> to solve a simple problem - decoupling the in-process sending of messages from handling messages.</p>\n<p>You can extend MediatR's <code>IRequest</code> interface with a custom <code>ICommand</code> and <code>IQuery</code> abstraction.\nThis allows you to define commands and queries in your system explicitly.</p>\n<p>On the write side, I typically use <a href=\"https://learn.microsoft.com/en-us/ef/core/\">EF Core</a> and a rich domain model to encapsulate business logic.\nThe command flow uses EF to load an entity into memory, execute the domain logic, and save the changes to the database.</p>\n<p>On the read side, I want as little indirection as possible.\nUsing <a href=\"https://github.com/DapperLib/Dapper\">Dapper</a> with raw SQL queries is an excellent choice.\nYou can also create views in the database and query them.\nAlternatively, you could use EF Core to execute queries with projections.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_060/cqrs_application.png\" alt=\"Diagram of an application using CQRS on the architectural level.\">\n<h2>Implementing CQRS With MediatR</h2>\n<p>Implementing CQRS with MediatR has two components:</p>\n<ul>\n<li>Defining your command or query class</li>\n<li>Implementing the respective command or query handler</li>\n</ul>\n<p>I made an in-depth video explaining this process, and you can <a href=\"https://youtu.be/vdi-p9StmG0\">watch it here.</a></p>\n<p>You use the <code>ISender</code> interface to <code>Send</code> the command or query.\nMediatR takes care of routing the command or query to the respective handler.</p>\n<p>The request will pass through the <em>request pipeline</em>.\nIt's a wrapper around each request, and you can use it to solve cross-cutting concerns with <a href=\"https://milanjovanovic.tech/blog/mediatr-pipeline-behaviors\"><strong><code>IPipelineBehavior</code></strong></a>.\nFor example, you can implement <a href=\"https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation\">validation for commands with FluentValidation.</a></p>\n<pre><code class=\"language-csharp\">[ApiController]\n[Route(&quot;api/bookings&quot;)]\npublic class BookingsController : ControllerBase\n{\n    private readonly ISender _sender;\n\n    public BookingsController(ISender sender)\n    {\n        _sender = sender;\n    }\n\n    [HttpPut(&quot;{id}/confirm&quot;)]\n    public async Task&lt;IActionResult&gt; ConfirmBooking(\n        Guid id,\n        CancellationToken cancellationToken)\n    {\n        var command = new ConfirmBookingCommand(id);\n\n        var result = await _sender.Send(command, cancellationToken);\n\n        if (result.IsFailure)\n        {\n            return BadRequest(result.Error);\n        }\n\n        return NoContent();\n    }\n}\n\n</code></pre>\n<p>Here's an example of a command handler with repositories and a rich domain model:</p>\n<pre><code class=\"language-csharp\">internal sealed class ConfirmBookingCommandHandler\n    : ICommandHandler&lt;ConfirmBookingCommand&gt;\n{\n    private readonly IDateTimeProvider _dateTimeProvider;\n    private readonly IBookingRepository _bookingRepository;\n    private readonly IUnitOfWork _unitOfWork;\n\n    public ConfirmBookingCommandHandler(\n        IDateTimeProvider dateTimeProvider,\n        IBookingRepository bookingRepository,\n        IUnitOfWork unitOfWork)\n    {\n        _dateTimeProvider = dateTimeProvider;\n        _bookingRepository = bookingRepository;\n        _unitOfWork = unitOfWork;\n    }\n\n    public async Task&lt;Result&gt; Handle(\n        ConfirmBookingCommand request,\n        CancellationToken cancellationToken)\n    {\n        var booking = await _bookingRepository.GetByIdAsync(\n            request.BookingId,\n            cancellationToken);\n\n        if (booking is null)\n        {\n            return Result.Failure(BookingErrors.NotFound);\n        }\n\n        var result = booking.Confirm(_dateTimeProvider.UtcNow);\n\n        if (result.IsFailure)\n        {\n            return result;\n        }\n\n        await _unitOfWork.SaveChangesAsync(cancellationToken);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>Here's an example of a query handler that uses Dapper and raw SQL:</p>\n<pre><code class=\"language-csharp\">internal sealed class SearchApartmentsQueryHandler\n    : IQueryHandler&lt;SearchApartmentsQuery, IReadOnlyList&lt;ApartmentResponse&gt;&gt;\n{\n    private static readonly int[] ActiveBookingStatuses =\n    {\n        (int)BookingStatus.Reserved,\n        (int)BookingStatus.Confirmed,\n        (int)BookingStatus.Completed\n    };\n\n    private readonly ISqlConnectionFactory _sqlConnectionFactory;\n\n    public SearchApartmentsQueryHandler(\n        ISqlConnectionFactory sqlConnectionFactory)\n    {\n        _sqlConnectionFactory = sqlConnectionFactory;\n    }\n\n    public async Task&lt;Result&lt;IReadOnlyList&lt;ApartmentResponse&gt;&gt;&gt; Handle(\n        SearchApartmentsQuery request,\n        CancellationToken cancellationToken)\n    {\n        if (request.StartDate &gt; request.EndDate)\n        {\n            return new List&lt;ApartmentResponse&gt;();\n        }\n\n        using var connection = _sqlConnectionFactory.CreateConnection();\n\n        const string sql = &quot;&quot;&quot;\n            SELECT\n                a.id AS Id,\n                a.name AS Name,\n                a.description AS Description,\n                a.price_amount AS Price,\n                a.price_currency AS Currency,\n                a.address_country AS Country,\n                a.address_state AS State,\n                a.address_zip_code AS ZipCode,\n                a.address_city AS City,\n                a.address_street AS Street\n            FROM apartments AS a\n            WHERE NOT EXISTS\n            (\n                SELECT 1\n                FROM bookings AS b\n                WHERE\n                    b.apartment_id = a.id AND\n                    b.duration_start &lt;= @EndDate AND\n                    b.duration_end &gt;= @StartDate AND\n                    b.status = ANY(@ActiveBookingStatuses)\n            )\n            &quot;&quot;&quot;;\n\n        var apartments = await connection\n            .QueryAsync&lt;ApartmentResponse, AddressResponse, ApartmentResponse&gt;(\n                sql,\n                (apartment, address) =&gt;\n                {\n                    apartment.Address = address;\n\n                    return apartment;\n                },\n                new\n                {\n                    request.StartDate,\n                    request.EndDate,\n                    ActiveBookingStatuses\n                },\n                splitOn: &quot;Country&quot;);\n\n        return apartments.ToList();\n    }\n}\n</code></pre>\n<h2>Closing Thoughts</h2>\n<p>Separating commands and queries can improve performance and scalability in the long run.\nYou can optimize commands and queries differently based on your requirements.</p>\n<p>Commands encapsulate complex business logic and validation.\nUsing EF Core and a rich domain model is an excellent solution.</p>\n<p>Queries are all about performance, so you want to use what's fastest.\nThis could be raw SQL queries with <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a>, EF Core projections, or Redis.</p>\n<p>If you want the system I use to build scalable applications with CQRS and MediatR, check out <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture.</strong></a></p>\n<p>Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr",
            "title": "CQRS Pattern With MediatR",
            "summary": "The CQRS pattern separates the writes and reads in the application, and that separation can be logical or physical.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_060.png",
            "date_modified": "2023-10-21T00:00:00.000Z",
            "date_published": "2023-10-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor",
            "content_html": "<p>Scrutor extends the built-in ASP.NET Core dependency injection container with two things the framework doesn't ship: assembly scanning and service decoration.\nAssembly scanning registers services by convention instead of one line per service.\nDecoration wraps an existing registration, which is how you add caching or permission checks without touching the original class.</p>\n<p>Dependency injection (DI) is one of the most exciting features of ASP.NET Core.\nIt helps us build more testable and maintainable applications.\nHowever, ASP.NET Core's built-in DI system sometimes needs a little help to achieve more advanced scenarios.</p>\n<p>So I want to introduce you to a powerful library for enhancing your ASP.NET Core DI - <a href=\"https://www.nuget.org/packages/Scrutor\">Scrutor.</a></p>\n<p>If you're an ASP.NET Core developer, you're already familiar with Dependency Injection.\nIt's a fundamental part of building modular and maintainable applications.</p>\n<p>Let's explore how Scrutor can simplify and enhance your DI setup.</p>\n<h2>What is Dependency Injection?</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0\">Dependency Injection</a>\nis a software design pattern used in ASP.NET Core to achieve the <a href=\"https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion\">Inversion of Control (IOC)</a>\nprinciple.\nThis promotes loose coupling and makes your code more testable, maintainable, and extensible.</p>\n<p>DI allows you to inject dependencies into your classes rather than create them within the class.\nThe framework takes care of providing the required instances at runtime.\nIt also manages the disposal of these dependencies based on the service lifetime.</p>\n<p>Here's an example of combining constructor and method injection in a controller:</p>\n<pre><code class=\"language-csharp\">[ApiController]\n[Route(&quot;api/activities&quot;)]\npublic class ActivitiesController : ControllerBase\n{\n    private readonly ILogger&lt;ActivitiesController&gt; _logger;\n\n    // Constructor injection\n    public ActivitiesController(ILogger&lt;ActivitiesController&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    [HttpGet]\n    public async Task&lt;IActionResult&gt; Get(ISender sender) // Method injection\n    {\n        var activities = await sender.Send(new GetActivitiesQuery());\n\n        return Ok(activities);\n    }\n}\n</code></pre>\n<h2>Service Lifetimes in ASP.NET Core</h2>\n<p>Before we dive into Scrutor, let's briefly discuss <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes\">service lifetimes in ASP.NET Core.</a>\nWhen you register a service in the DI container, you specify its lifetime.\nThe service lifetime defines how long the DI container should maintain the service.</p>\n<p>ASP.NET Core provides three main lifetimes:</p>\n<ul>\n<li><strong>Singleton</strong>: A single instance of the service is created and reused throughout the application's lifetime.</li>\n<li><strong>Scoped</strong>: A new instance is created for each scope (usually a web request). Services created in the same scope share the same instance.</li>\n<li><strong>Transient</strong>: A new instance is created every time the service is requested.</li>\n</ul>\n<p>Understanding <a href=\"https://milanjovanovic.tech/blog/using-scoped-services-from-singletons-in-aspnetcore\"><strong>service lifetimes</strong></a> is crucial when designing your application's architecture.</p>\n<h2>What is Scrutor?</h2>\n<p>The <a href=\"https://github.com/khellang/Scrutor\">Scrutor library</a> improves your dependency injection code by extending the existing features from <code>Microsoft.Extensions.DependencyInjection</code>.</p>\n<p>These extensions add support for advanced assembly scanning and service decoration.</p>\n<p>To get started using Scrutor, you need to install the NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package Scrutor\n</code></pre>\n<h2>Assembly Scanning With Scrutor</h2>\n<p>One of the most powerful features of Scrutor is its ability to perform assembly scanning.\nRather than manually registering each service, Scrutor allows you to scan your assemblies for types that should be registered with the DI container.\nThis can significantly reduce the boilerplate code required for service registration, making your code cleaner and more maintainable.</p>\n<p>The entry point for assembly scanning is the <code>Scan</code> method, which accepts a delegate to define the DI setup.</p>\n<p>Here's an example of scanning two assemblies and registering the classes inside as scoped services:</p>\n<pre><code class=\"language-csharp\">builder.Services.Scan(selector =&gt; selector\n    .FromAssemblies(\n        typeof(PersistenceAssembly).Assembly,\n        typeof(InfrastructureAssembly).Assembly)\n    .AddClasses(publicOnly: false)\n    .UsingRegistrationStrategy(RegistrationStrategy.Skip)\n    .AsMatchingInterface()\n    .WithScopedLifetime());\n</code></pre>\n<p>Let's unpack what's happening here:</p>\n<ul>\n<li><code>FromAssemblies</code> - allows you to specify which assemblies to scan</li>\n<li><code>AddClasses</code> - adds the classes from the selected assemblies</li>\n<li><code>UsingRegistrationStrategy</code> - defines which <code>RegistrationStrategy</code> to use</li>\n<li><code>AsMatchingInterface</code> - registers the types as matching interfaces (<code>ClassName</code> → <code>IClassName</code>)</li>\n<li><code>WithScopedLifetime</code> - registers the types with a scoped service lifetime</li>\n</ul>\n<p>There are three values for <code>RegistrationStrategy</code> you can use:</p>\n<ul>\n<li><code>RegistrationStrategy.Skip</code> - skips registrations if service already exists</li>\n<li><code>RegistrationStrategy.Append</code>- appends a new registration for existing services</li>\n<li><code>RegistrationStrategy.Throw</code>- throws when trying to register an existing service</li>\n</ul>\n<p>You can also specify a filter to <code>AddClasses</code> to select specific types you want to configure.\nHere's an example of registering repository implementations:</p>\n<pre><code class=\"language-csharp\">services.Scan(scan =&gt; scan\n    .FromAssemblies(typeof(PersistenceAssembly).Assembly)\n    .AddClasses(\n        filter =&gt; filter.Where(x =&gt; x.Name.EndsWith(&quot;Repository&quot;)),\n        publicOnly: false)\n    .UsingRegistrationStrategy(RegistrationStrategy.Throw)\n    .AsMatchingInterface()\n    .WithScopedLifetime());\n</code></pre>\n<h2>Service Decoration With Scrutor</h2>\n<p>Service decoration is another valuable feature offered by Scrutor.\nIt enables you to modify or extend services during registration without changing the original implementation.</p>\n<p>This is incredibly useful when adding <a href=\"https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture\"><strong>cross-cutting concerns</strong></a> or other modifications to services without altering their core functionality.\nFor example, you can implement a <a href=\"https://milanjovanovic.tech/blog/decorator-pattern-in-asp-net-core\">caching decorator for repositories.</a></p>\n<p>Here's how you can configure a decorator with Scrutor's <code>Decorate</code> method:</p>\n<pre><code class=\"language-csharp\">services.AddScoped&lt;IActivitiesRepository, ActivitiesRepository&gt;();\n\nservices.Decorate&lt;IActivitiesRepository, PermissionActivitiesRepository&gt;();\n</code></pre>\n<p>It will decorate the <code>ActivitiesRepository</code> service using the <code>PermissionActivitiesRepository</code>.\nThis also means that <code>PermissionActivitiesRepository</code> can inject an <code>IActivitiesRepository</code> instance, and at runtime, this is resolved as <code>ActivitiesRepository</code>.</p>\n<p>Here's how you can implement the <code>PermissionActivitiesRepository</code>:</p>\n<pre><code class=\"language-csharp\">public class PermissionActivitiesRepository : IActivitiesRepository\n{\n    private readonly IActivitiesRepository _decorated;\n    private readonly IPermissionChecker _permissionChecker;\n\n    public PermissionActivitiesRepository(\n        IActivitiesRepository decorated,\n        IPermissionChecker permissionChecker)\n    {\n        _decorated = decorated;\n        _permissionChecker = permissionChecker;\n    }\n\n    public List&lt;Activity&gt; Get()\n    {\n        if (!_permissionChecker.HasPermission(Permissions.FetchActivities))\n        {\n            return new();\n        }\n\n        return _decorated.Get();\n    }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Scrutor can improve your ASP.NET Core DI by simplifying service registration through assembly scanning and enabling service decoration.\nYou can use Scrutor's capabilities to write cleaner, more maintainable, and flexible DI code while reducing the complexity of your startup configuration.</p>\n<p>Assembly scanning can reduce the boilerplate code required for service registration.\nIt also allows you to create custom conventions for registering services.</p>\n<p>Service decoration has been a real game-changer for me.\nIt's the simplest way to introduce cross-cutting concerns in your application.\nFor example, I used to add an idempotency check before handling events.</p>\n<p>Hope this was valuable.</p>\n<p>Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/improving-aspnetcore-dependency-injection-with-scrutor",
            "title": "Improving ASP.NET Core Dependency Injection With Scrutor",
            "summary": "ASP.NET Core's built-in DI system sometimes needs a little help to achieve more advanced scenarios.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_059.png",
            "date_modified": "2023-10-14T00:00:00.000Z",
            "date_published": "2023-10-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/getting-started-with-nservicebus-in-dotnet",
            "content_html": "<p>To get started with NServiceBus, install the <code>NServiceBus.Extensions.Hosting</code> package, call <code>UseNServiceBus</code> on the host, and configure a transport such as Azure Service Bus.\nThen define messages as <code>ICommand</code>, <code>IEvent</code>, or <code>IMessage</code> classes, publish them through <code>IMessageSession</code>, and handle them by implementing <code>IHandleMessages&lt;T&gt;</code>.</p>\n<p>NServiceBus is a feature-rich messaging framework supporting many different message transports.\nIt's developed and maintained by <a href=\"https://particular.net/\">Particular Software.</a>\nAnd it simplifies the process of building complex distributed systems across various cloud-based queueing technologies.</p>\n<p>The basic building blocks of NServiceBus are messages and endpoints.\nA message contains the required information to execute a business operation.\nEndpoints are logical entities that send and receive messages.</p>\n<p>And now let's see how to get started with NServiceBus, from installation and setup to building your first NServiceBus endpoint.</p>\n<p>In this week's newsletter, you will learn how to:</p>\n<ul>\n<li>Configure an endpoint to use Azure Service Bus</li>\n<li>Send and publish messages using <code>IMessageSession</code></li>\n<li>Handle messages with NServiceBus</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>What is NServiceBus?</h2>\n<p><a href=\"https://go.particular.net/milanjovanovic\">NServiceBus</a> is a messaging framework and platform that simplifies building reliable, scalable, and maintainable distributed systems.\nIt's designed to address the challenges that arise when building applications that are distributed across multiple servers.</p>\n<p>One of NServiceBus's foundational principles is its embrace of a message-driven architecture.\nIn this model, components communicate by sending and receiving messages.\nMessages are the fundamental units of communication, representing commands, events, or data that services exchange.</p>\n<p>Why is this significant?</p>\n<p>Message-driven architectures offer several advantages:</p>\n<ul>\n<li>Asynchronous communication</li>\n<li>Loose coupling</li>\n<li>Reliability</li>\n</ul>\n<p>NServiceBus supports the powerful publish/subscribe (pub/sub) messaging pattern.\nThis pattern allows services to publish events and subscribe to events of interest.\nWhen a service publishes an event, all interested subscribers receive a copy of the event.\nThis is a key feature for building <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>event-driven architectures</strong></a>, where services react to and process events in response to various actions or changes in the system.</p>\n<h2>Configuring the NServiceBus Endpoint</h2>\n<p>NServiceBus uses the concept of an <em>endpoint</em> to send and receive messages.\nIt's a logical component that communicates with other components.\nYou define your message handlers and sagas inside of an endpoint.</p>\n<p>Let's start by installing the <code>NServiceBus</code> NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package NServiceBus.Extensions.Hosting\n</code></pre>\n<p>Now you can configure an <em>endpoint</em> to use <a href=\"https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview\">Azure Service Bus</a>\nto send messages:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder();\n\nbuilder.Host.UseNServiceBus(context =&gt;\n{\n    var endpointConfiguration = new EndpointConfiguration(&quot;Training&quot;);\n\n    var transport = endpointConfiguration\n        .UseTransport&lt;AzureServiceBusTransport&gt;();\n\n    var connectionString = builder.Configuration\n        .GetConnectionString(&quot;AzureServiceBusConnectionString&quot;);\n    transport.ConnectionString(connectionString);\n\n    endpointConfiguration.EnableInstallers();\n\n    return endpointConfiguration;\n});\n\nvar app = builder.Build();\n\napp.Run();\n</code></pre>\n<p>The call to <code>UseNServiceBus</code> tells the host to use NServiceBus.\nInside the callback, you can configure the endpoint that will start when the host runs.</p>\n<p>One more important aspect is calling <code>EnableInstallers</code> to set up the Azure Service Bus topology.\nThis will tell NServiceBus to create the required queues, so you don't have to do it manually.</p>\n<h2>Publishing Messages in NServiceBus</h2>\n<p>The next building block you need in any messaging system is the messages.\nMessages are C# classes or interfaces that contain meaningful data for the business process.</p>\n<p>NServiceBus supports three types of messages:</p>\n<ul>\n<li><code>ICommand</code> - sends a request to perform an action</li>\n<li><code>IEvent</code> - communicates that something significant occurred</li>\n<li><code>IMessage</code> - for messages that aren't commands or events (typically for replies in <em>request-response</em>)</li>\n</ul>\n<p>Events can have more than one handler, while a command should have only one handler.</p>\n<p>Let's create our first message contract:</p>\n<pre><code class=\"language-csharp\">using NServiceBus;\n\npublic class WorkoutCreated : IEvent\n{\n    public Guid Id [ get; set; ]\n}\n</code></pre>\n<p>The <code>WorkoutCreated</code> message is an event that we will publish after creating a new <code>Workout</code>.</p>\n<p>You can use the <code>IMessageSession</code> service to send messages from your controllers or Minimal API endpoints.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;api/workouts&quot;, async (\n    Workout workout,\n    AppDbContext context,\n    IMessageSession messageSession) =&gt;\n{\n    context.Add(workout);\n\n    await context.SaveChangesAsync();\n\n    await messageSession.Publish(new  WorkoutCreated { Id = workout.Id });\n\n    return Results.Ok(workout);\n});\n</code></pre>\n<p>NServiceBus has some built-in validation when sending messages.\nYou have to specify an <code>ICommand</code> when calling the <code>Send</code> method, or you will get an exception.\nSimilarly, you have to specify an <code>IEvent</code> when calling the <code>Publish</code> method.</p>\n<h2>Handling Messages With NServiceBus</h2>\n<p>Once you send a message, you need a way to handle it and run some business logic.\nTo handle a message, you need to implement the <code>IHandleMessages</code> interface and specify which message you are handling.</p>\n<p>Here's an implementation of the <code>WorkoutCreatedHandler</code>:</p>\n<pre><code class=\"language-csharp\">public class WorkoutCreatedHandler : IHandleMessages&lt;WorkoutCreated&gt;\n{\n    private readonly ILogger&lt;WorkoutCreated&gt; _logger;\n\n    public WorkoutCreatedHandler(ILogger&lt;WorkoutCreated&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task Handle(\n        WorkoutCreated message,\n        IMessageHandlerContext context)\n    {\n        logger.LogInformation(&quot;Processing workout - {Id}&quot;, message.Id);\n\n        // Continue to process the message.\n    }\n}\n</code></pre>\n<p>Implementing <code>IHandleMessages&lt;WorkoutCreated&gt;</code> tells NServiceBus how to process the <code>WorkoutCreated</code> message when an endpoint receives it.\nThis interface defines only one method: <code>Handle</code>.</p>\n<p>The <code>Handle</code> method has an <code>IMessageHandlerContext</code> parameter, which allows you to send more messages.\nThis can be helpful when implementing a <a href=\"https://milanjovanovic.tech/blog/saga-pattern-dotnet\"><strong>choreographed saga</strong></a>.\nProcessing one message triggers the next step in the chain until the entire process is completed.</p>\n<h2>In Summary</h2>\n<p>In this week's issue, we discussed NServiceBus, a robust messaging framework for building distributed systems in .NET.\nYou learned how to configure NServiceBus with the Azure Service Bus transport.\nWe discussed the different message types in NServiceBus and how to publish and handle a message.</p>\n<p>Building distributed systems is a complex endeavor, but NServiceBus simplifies many challenges.\nBy embracing a message-driven architecture and leveraging NServiceBus's features,\nyou'll be well-equipped to create resilient, scalable, and maintainable applications in the .NET ecosystem.</p>\n<p>Further reading:</p>\n<ul>\n<li><a href=\"https://go.particular.net/milanjovanovic/getting-started-with-nservicebus\">NServiceBus step-by-step tutorial</a></li>\n<li><a href=\"https://go.particular.net/milanjovanovic/live-coding-your-first-nservicebus-system\">Live coding an NServiceBus system</a></li>\n<li><a href=\"https://go.particular.net/milanjovanovic/monitoring-demo\">NServiceBus monitoring demo</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus\">Messaging with Azure Service Bus</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq\">Implementing the Saga pattern</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/orchestration-vs-choreography\">Orchestration vs Choreography</a></li>\n</ul>\n<p>Hope this was helpful.</p>\n<p>I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/getting-started-with-nservicebus-in-dotnet",
            "title": "Getting Started With NServiceBus in .NET",
            "summary": "NServiceBus is a feature-rich messaging framework supporting many different message transports. Its basic building blocks are messages and endpoints.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_058.png",
            "date_modified": "2023-10-07T00:00:00.000Z",
            "date_published": "2023-10-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation",
            "content_html": "<p>You validate CQRS commands with a MediatR <code>IPipelineBehavior</code> that runs FluentValidation validators before the handler executes.\nInput validation only checks that the command is processable, while business validation reads the system state to check business rules.\nA middleware turns the resulting <code>ValidationException</code> into a <code>ProblemDetails</code> response with status 400.</p>\n<p>Validation is an essential <a href=\"https://milanjovanovic.tech/blog/balancing-cross-cutting-concerns-in-clean-architecture\"><strong>cross-cutting concern</strong></a> that you need to solve in your application.\nYou want to ensure the request is valid before you consider processing it.</p>\n<p>Another important question you need to answer is how you approach different types of validation.\nFor example, I consider input and business validation differently, and each deserves a specific solution.</p>\n<p>I want to show you an elegant solution for validation using <a href=\"https://github.com/jbogard/MediatR\">MediatR</a>\nand <a href=\"https://docs.fluentvalidation.net/en/latest/index.html\">FluentValidation.</a></p>\n<p>If you aren't using <a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs\">CQRS</a> with MediatR, don't worry.\nEverything I explain about validation can easily be adapted to other paradigms.</p>\n<p>Here's what I'm going to talk about in this week's newsletter:</p>\n<ul>\n<li>Standard validation approach</li>\n<li>Input vs business validation</li>\n<li>Separating validation logic</li>\n<li>Generic <code>ValidationBehavior</code></li>\n</ul>\n<p>Let's dive in.</p>\n<h2>The Standard Command Validation Approach</h2>\n<p>The standard way of implementing validation is right before processing the command.\nThe validation is tightly coupled to the command handler, which could be problematic.</p>\n<p>I find this approach difficult to maintain as the complexity of the validation increases.\nEach change to the validation logic also touches the handler, and the handler itself can grow out of control.</p>\n<p>It also makes it harder to differentiate between input and <em>business</em> validation.</p>\n<p>Here's an example <code>ShipOrderCommandHandler</code> that checks if the <code>ShippingAddress.Country</code> is one of the supported countries:</p>\n<pre><code class=\"language-csharp\">internal sealed class ShipOrderCommandHandler\n    : IRequestHandler&lt;ShipOrderCommand&gt;\n{\n    private readonly IOrderRepository _orderRepository;\n    private readonly IShippingService _shippingService;\n    private readonly ShipmentSettings _shipmentSettings;\n\n    public async Task Handle(\n        ShipOrderCommand command,\n        CancellationToken cancellationToken)\n    {\n        if (!_shipmentSettings\n                .SupportedCountries\n                .Contains(command.ShippingAddress.Country))\n        {\n            throw new ArgumentException(nameof(ShipOrderCommand.Address));\n        }\n\n        var order = _orderRepository.Get(command.OrderId);\n\n        _shippingService.ShipTo(\n            command.ShippingAddress,\n            command.ShippingMethod);\n    }\n}\n</code></pre>\n<p>What if we can separate command validation and command handling?</p>\n<h2>Input Validation and Business Validation</h2>\n<p>I mentioned input and <em>business</em> validation in the previous section.</p>\n<p>Here's how I consider them to be different:</p>\n<ul>\n<li><strong>Input validation</strong> - We only validate that the command is <em>processable</em>.\nThese are simple validations, such as checking for <code>null</code> values, empty strings, etc.</li>\n<li><strong>Business validation</strong> - We validate the command to satisfy the business rules.\nThis includes checking the system state for required preconditions before processing the command.</li>\n</ul>\n<p>Another way to compare them is cheap vs. expensive.\nInput validation is usually cheap to execute and can be done in memory.\nWhile business validation involves reading state and is slower.</p>\n<p>So, input validation sits at the entry point of the use case before handling the request.\nAfter it completes, we have a <em>valid</em> command.\nAnd this is a rule I always follow - an invalid command should never reach the handler.</p>\n<h2>Input Validation With FluentValidation</h2>\n<p><a href=\"https://docs.fluentvalidation.net/en/latest/index.html\">FluentValidation</a> is an excellent validation library for .NET,\nwhich uses a fluent interface and lambda expressions for building strongly typed validation rules.</p>\n<p>Here's the <code>ShipOrderCommand</code> that we want to validate:</p>\n<pre><code class=\"language-csharp\">public sealed record ShipOrderCommand : IRequest\n{\n    public Guid OrderId { get; set; }\n\n    public string ShippingMethod { get; set; }\n\n    public Address ShippingAddress { get; set; }\n}\n</code></pre>\n<p>To implement a validator with <a href=\"https://github.com/FluentValidation/FluentValidation\">FluentValidation,</a>\nyou create a class that inherits from the <code>AbstractValidator&lt;T&gt;</code> base class.\nThen, you can add the validation rules from the constructor using <code>RuleFor</code>:</p>\n<pre><code class=\"language-csharp\">public sealed class ShipOrderCommandValidator\n    : AbstractValidator&lt;ShipOrderCommand&gt;\n{\n    public ShipOrderCommandValidator(ShipmentSettings settings)\n    {\n        RuleFor(command =&gt; command.OrderId)\n            .NotEmpty()\n            .WithMessage(&quot;The order identifier can't be empty.&quot;);\n\n        RuleFor(command =&gt; command.ShippingMethod)\n            .NotEmpty()\n            .WithMessage(&quot;The shipping method can't be empty.&quot;);\n\n        RuleFor(command =&gt; command.ShippingAddress)\n            .NotNull()\n            .WithMessage(&quot;The shipping address can't be empty.&quot;);\n\n        RuleFor(command =&gt; command.ShippingAddress.Country)\n            .Must(country =&gt; settings.SupportedCountries.Contains(country))\n            .WithMessage(&quot;The shipping country isn't supported.&quot;);\n    }\n}\n</code></pre>\n<p>The naming convention I like to use is the name of the command and append <em>Validator</em>.\nYou can also enforce this by writing <a href=\"https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests\">architecture tests.</a></p>\n<p>To automatically register all validators from an assembly, you need to call the <code>AddValidatorsFromAssembly</code> method:</p>\n<pre><code class=\"language-csharp\">services.AddValidatorsFromAssembly(ApplicationAssembly.Assembly);\n</code></pre>\n<h2>Running Validation From the Use Case</h2>\n<p>To run the <code>ShipOrderCommandValidator</code>, you can use the <code>IValidator&lt;T&gt;</code> service and inject it from the constructor.</p>\n<p>The validator exposes a few methods you can call, like <code>Validate</code>, <code>ValidateAsync</code>, or <code>ValidateAndThrow</code>.</p>\n<p>The <code>Validate</code> method returns a <code>ValidationResult</code> object which contains two properties:</p>\n<ul>\n<li><code>IsValid</code> - a boolean flag saying whether the validation succeeded</li>\n<li><code>Errors</code> - a collection of <code>ValidationFailure</code> objects containing any validation failures</li>\n</ul>\n<p>Alternatively, calling the <code>ValidateAndThrow</code> method throws a <code>ValidationException</code> if validation fails.</p>\n<pre><code class=\"language-csharp\">internal sealed class ShipOrderCommandHandler\n    : IRequestHandler&lt;ShipOrderCommand&gt;\n{\n    private readonly IOrderRepository _orderRepository;\n    private readonly IShippingService _shippingService;\n    private readonly IValidator&lt;ShipOrderCommand&gt; _validator;\n\n    public async Task Handle(\n        ShipOrderCommand command,\n        CancellationToken cancellationToken)\n    {\n        _validator.ValidateAndThrow(command);\n\n        var order = _orderRepository.Get(command.OrderId);\n\n        _shippingService.ShipTo(\n            command.ShippingAddress,\n            command.ShippingMethod);\n    }\n}\n</code></pre>\n<p>This approach forces you to define an explicit dependency on <code>IValidator</code> in every command handler.</p>\n<p>What if we can implement this cross-cutting concern in a more generic way?</p>\n<h2>MediatR Validation Pipeline</h2>\n<p>Here's a complete implementation of a <code>ValidationBehavior</code> using FluentValidation and <a href=\"https://milanjovanovic.tech/blog/mediatr-pipeline-behaviors\"><strong>MediatR's <code>IPipelineBehavior</code></strong></a>.</p>\n<p>The <code>ValidationBehavior</code> acts as a middleware for the request pipeline and performs validation.\nIf the validation fails, it will throw a custom <code>ValidationException</code> with a collection of <code>ValidationError</code> objects.</p>\n<p>I also want to highlight the use of <code>ValidateAsync</code>, which allows you to define asynchronous validation rules.\nYou must call the <code>ValidateAsync</code> method if you have asynchronous rules.\nOtherwise, the validator will throw an exception.</p>\n<pre><code class=\"language-csharp\">public sealed class ValidationBehavior&lt;TRequest, TResponse&gt;\n    : IPipelineBehavior&lt;TRequest, TResponse&gt;\n    where TRequest : ICommandBase\n{\n    private readonly IEnumerable&lt;IValidator&lt;TRequest&gt;&gt; _validators;\n\n    public ValidationBehavior(IEnumerable&lt;IValidator&lt;TRequest&gt;&gt; validators)\n    {\n        _validators = validators;\n    }\n\n    public async Task&lt;TResponse&gt; Handle(\n        TRequest request,\n        RequestHandlerDelegate&lt;TResponse&gt; next,\n        CancellationToken cancellationToken)\n    {\n        var context = new ValidationContext&lt;TRequest&gt;(request);\n\n        var validationFailures = await Task.WhenAll(\n            _validators.Select(validator =&gt; validator.ValidateAsync(context)));\n\n        var errors = validationFailures\n            .Where(validationResult =&gt; !validationResult.IsValid)\n            .SelectMany(validationResult =&gt; validationResult.Errors)\n            .Select(validationFailure =&gt; new ValidationError(\n                validationFailure.PropertyName,\n                validationFailure.ErrorMessage))\n            .ToList();\n\n        if (errors.Any())\n        {\n            throw new Exceptions.ValidationException(errors);\n        }\n\n        var response = await next();\n\n        return response;\n    }\n}\n</code></pre>\n<p>Don't forget to register the <code>ValidationBehavior</code> with MediatR by calling <code>AddOpenBehavior</code>:</p>\n<pre><code class=\"language-csharp\">services.AddMediatR(config =&gt;\n{\n    config.RegisterServicesFromAssemblyContaining&lt;ApplicationAssembly&gt;();\n\n    config.AddOpenBehavior(typeof(ValidationBehavior&lt;,&gt;));\n});\n</code></pre>\n<h2>Handling Validation Exceptions</h2>\n<p>Here's a custom <code>ValidationExceptionHandlingMiddleware</code> middleware that only handles the custom <code>ValidationException</code>.\nIt converts the exception to a <code>ProblemDetails</code> response and includes any validation errors.</p>\n<p>You can easily expand this to be a <a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\"><strong>generic global exception handler</strong></a>.</p>\n<pre><code class=\"language-csharp\">public sealed class ValidationExceptionHandlingMiddleware\n{\n    private readonly RequestDelegate _next;\n\n    public ValidationExceptionHandlingMiddleware(RequestDelegate next)\n    {\n        _next = next;\n    }\n\n    public async Task InvokeAsync(HttpContext context)\n    {\n        try\n        {\n            await _next(context);\n        }\n        catch (Exceptions.ValidationException exception)\n        {\n            var problemDetails = new ProblemDetails\n            {\n                Status = StatusCodes.Status400BadRequest,\n                Type = &quot;ValidationFailure&quot;,\n                Title = &quot;Validation error&quot;,\n                Detail = &quot;One or more validation errors has occurred&quot;\n            };\n\n            if (exception.Errors is not null)\n            {\n                problemDetails.Extensions[&quot;errors&quot;] = exception.Errors;\n            }\n\n            context.Response.StatusCode = StatusCodes.Status400BadRequest;\n\n            await context.Response.WriteAsJsonAsync(problemDetails);\n        }\n    }\n}\n</code></pre>\n<p>You also need to include the middleware in the request pipeline by calling <code>UseMiddleware</code>:</p>\n<pre><code class=\"language-csharp\">app.UseMiddleware&lt;ExceptionHandlingMiddleware&gt;();\n</code></pre>\n<h2>Takeaway</h2>\n<p>This implementation of <code>ValidationBehavior</code> is something I use in real projects, and it works incredibly well.\nIf I don't want to throw an exception, I can update the <code>ValidationBehavior</code> to return a result object instead.</p>\n<p>How do you apply this if you're not using MediatR?</p>\n<p>I'm using an <code>IPipelineBehavior</code>, which allows me to implement a <em>middleware</em> wrapping each request.</p>\n<p>So, all you need is a way to implement middleware and place your validation inside.\nAnd I like having options, so here are <a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core\">three ways to create middleware in ASP.NET Core.</a></p>\n<p>Hope this was valuable.</p>\n<p>Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/cqrs-validation-with-mediatr-pipeline-and-fluentvalidation",
            "title": "CQRS Validation with MediatR Pipeline and FluentValidation",
            "summary": "I consider input and business validation differently, and each deserves a specific solution.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_057.png",
            "date_modified": "2023-09-30T00:00:00.000Z",
            "date_published": "2023-09-30T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps",
            "content_html": "<p>A modular monolith makes the move to microservices easier because it forces you to solve coupling first.\nYou identify bounded contexts, keep each module's tables private, and have modules communicate through a public API or messaging.\nExtracting a module then means moving it into its own process behind a reverse proxy.</p>\n<p>You start building a beautiful monolith system.\nMaybe a <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\"><strong>modular monolith</strong></a>.</p>\n<p>The system grows over time, and requirements are ever-changing. Slowly, cracks begin to appear in the system.</p>\n<p>This could be for organizational reasons and distributing the work across a team.\nOr it could be because of scaling issues and performance bottlenecks.</p>\n<p>You begin evaluating the possible solutions, and the benefits and tradeoffs of each one.\nAt last, you come to a decision.</p>\n<p>It's time to migrate parts of the system to individual (micro)services.</p>\n<p>So, how do we approach this migration from monolith to microservices?</p>\n<p>That is the topic of this week's newsletter.</p>\n<p>Let's dive in!</p>\n<h2>Decoupling Using Bounded Contexts</h2>\n<p>The first step in moving from a monolith to microservices is identifying the bounded contexts.\nBecause they represent cohesive parts of the domain that are candidates for extraction.</p>\n<p>One solution is to identify <a href=\"https://martinfowler.com/bliki/BoundedContext.html\">bounded contexts</a>\nusing the domain-driven design strategic modeling.</p>\n<p>Bounded contexts define the explicit boundaries between modules and separate the responsibilities.\nThis is one of the biggest challenges when migrating to microservices.\n<a href=\"https://learn.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis\">Identifying good boundaries</a>\nbetween modules ensures microservices are narrowly focused on one problem domain.</p>\n<p>Defining boundaries is also easier in a monolith because you aren't working with a distributed system.\n<a href=\"https://cloud.google.com/architecture/microservices-architecture-refactoring-monoliths\">Refactoring bad boundaries</a>\nis less risky, and you have more freedom to &quot;get it right&quot;.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_056/bounded_contexts.png\" alt=\"Bounded contexts.\">\n<p>And the size of the bounded context shouldn't worry you.\nInstead, focus on <a href=\"https://docs.particular.net/architecture/azure/microservices\">service boundaries.</a></p>\n<p>The next problem you need to solve is coupling.\nCoupling is manifested in two ways:</p>\n<ul>\n<li>Database dependencies</li>\n<li>Communication between modules</li>\n</ul>\n<p>You can solve these problems from the start by building a modular monolith.\nBut I'll also explain the guiding principles you can use to solve coupling.</p>\n<h2>How a Modular Monolith Solves Coupling</h2>\n<p>A <a href=\"https://milanjovanovic.tech/blog/what-is-a-modular-monolith\">modular monolith</a> is a catchy name for a monolith system built from a few\nbounded contexts (modules) and following a set of principles to control coupling.\nEach module contains a cohesive set of functionalities and is isolated from other modules in the system.\nThe isolation refers to database dependencies and inter-module communication.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_056/modular_monolith.png\" alt=\"Modular monolith.\">\n<p>You can think of a module as a distinct application within the system.\nA module has its own domain, entities, use cases, database tables.\nThe modules are deployed together as a single executable application.\nBut they are otherwise independent.</p>\n<p>You can apply different architectural approaches to each module, like <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Clean Architecture</strong></a>.</p>\n<p>So I mentioned that you need to reduce the coupling between modules.</p>\n<p>Here are two principles to solve database coupling:</p>\n<ul>\n<li>Modules can't share tables in the database</li>\n<li>Modules can't directly query the database tables of other modules</li>\n</ul>\n<p>Sharing database tables leads to a high degree of coupling, and this is exactly what you are trying to avoid.\nYou can <a href=\"https://milanjovanovic.tech/blog/schema-per-module-vs-database-per-module\"><strong>isolate the data for each module</strong></a> on a logical level using schemas or physically with different databases.</p>\n<p>A module should expose a public API that other modules can call.\nThis public API is the entry point into the module.\nAnd this is the only way for modules to communicate.</p>\n<p>Communication between modules can be <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns#synchronous-communication-with-method-calls\"><strong>synchronous</strong></a>\nusing method calls, or <a href=\"https://milanjovanovic.tech/blog/modular-monolith-communication-patterns#asynchronous-communication-with-messaging\"><strong>asynchronous</strong></a>\nusing a message bus.</p>\n<p>My preferred approach is asynchronous communication using messaging.\nIt's loosely coupled and makes the transition to microservices easier.</p>\n<h2>Adding a Message Broker To The System</h2>\n<p>To implement asynchronous communication between modules, you can introduce a message broker.\nBut you don't need to introduce a full-blown message broker from the start.</p>\n<p>You can implement messaging between modules using an abstraction like <a href=\"https://masstransit.io\">MassTransit</a> while abstracting away the transport mechanism.</p>\n<p>MassTransit has an in-memory transport that works well inside a single process.\nIt's very fast.\nBut it isn't durable, and you can lose messages if the bus is stopped.</p>\n<p>You only need to configure a different transport mechanism when introducing a real message broker.\nBut you don't need to change your messaging code.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_056/modular_monolith_queue.png\" alt=\"Modular monolith with a queue.\">\n<p>What is the purpose of messaging inside a modular monolith?</p>\n<p>Designing your system like this makes the modules loosely coupled and independent.\nThe price you pay in increased complexity at the start is justified as the project matures.</p>\n<h2>Extracting Modules to Microservices</h2>\n<p>We decided to move from a monolith system to microservices.\nSince we built our system in a modular way, the migration comes down to extracting a module into a new process.</p>\n<p>You should introduce a <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>reverse proxy</strong></a> in front of your services to route incoming traffic.\nThis will hide the implementation details of the microservices system from client applications.</p>\n<p>The new microservice needs to connect to the message bus, but we don't need to change anything in our code.\nUsing messaging for communication between modules simplifies the migration process.\nThis might remind you of <a href=\"https://go.particular.net/break-that-big-ball-of-mud\">event-driven architecture</a>.</p>\n<p>If you implement inter-module communication using method calls, you must replace that implementation with HTTP calls over the network.\nBecause you're now building a distributed system, and the previous implementation using method calls will not work.\nYou also need to consider authentication, fault tolerance...</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_056/extracting_modules.png\" alt=\"Microservices with extracting modules.\">\n<p><a href=\"https://milanjovanovic.tech/blog/breaking-it-down-how-to-migrate-your-modular-monolith-to-microservices\"><strong>Extracting modules</strong></a> from the monolith system leads to replacing all the functionalities of the old system with new microservices.\nThis process of migrating to microservices follows the <a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig\">strangler fig pattern.</a></p>\n<h2>Closing Thoughts</h2>\n<p>The biggest blocker for moving from a monolith to microservices is coupling.\nCoupling is a change preventer.\nSo, this is the first thing you need to tackle.</p>\n<p>You need to solve coupling at the database level and between components in the code.\nBuilding the system in a modular way can prevent these problems from the start.</p>\n<p>Which is why a Modular monolith is an excellent approach.</p>\n<p>You can identify bounded contexts in the system and use them as the boundaries in the monolith.\nAnd getting the boundaries right is easier in a monolith.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/breaking-it-down-how-to-migrate-your-modular-monolith-to-microservices\"><strong>Migrating to microservices</strong></a> comes down to extracting the modules into individual services.</p>\n<p>Of course, you still need to think about security and fault tolerance because you now have a distributed system.</p>\n<p>Talking about architecture in abstract terms can be difficult to grasp, but it's important when discussing conceptual solutions.</p>\n<p>I'll show you a practical Modular monolith implementation soon to complete the circle.</p>\n<p>Until then, I hope this was valuable.</p>\n<p>See you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/monolith-to-microservices-how-a-modular-monolith-helps",
            "title": "Monolith to Microservices: How a Modular Monolith Helps",
            "summary": "You start building a beautiful monolith system, maybe a modular monolith. The system grows, cracks begin to appear, and at last you decide it's time to migrate…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_056.png",
            "date_modified": "2023-09-23T00:00:00.000Z",
            "date_published": "2023-09-23T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/feature-flags-in-dotnet-and-how-i-use-them-for-ab-testing",
            "content_html": "<p>Feature flags let you turn application features on or off at runtime without redeploying the code.\nIn .NET you get them from the <code>Microsoft.FeatureManagement</code> library, which builds on the configuration system and exposes flag state through <code>IFeatureManager</code>.\nPercentage and targeting filters turn that switch into phased rollouts and A/B tests.</p>\n<p>The ability to conditionally turn features on or off in your application without redeploying the code is a powerful tool.</p>\n<p>It lets you quickly iterate on new features and frequently integrate your changes with the main branch.</p>\n<p>You can use <strong>feature flags</strong> to achieve this.</p>\n<p><strong>Feature flags</strong> are a software development technique that allows you to wrap application features in a conditional statement.\nYou can then toggle the feature on or off in runtime to control which features are enabled.</p>\n<p>We have a lot to cover in this week's newsletter:</p>\n<ul>\n<li>Feature flag fundamentals in .NET</li>\n<li>Feature filters and phased rollouts</li>\n<li>Trunk-based development</li>\n<li>A/B testing</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>Feature Flags In .NET</h2>\n<p><a href=\"https://github.com/microsoft/FeatureManagement-Dotnet\">Feature flags</a> provide a way for .NET and ASP.NET Core applications to turn features on or off dynamically.</p>\n<p>To get started, you need to install the <code>Microsoft.FeatureManagement</code> library in your project:</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.FeatureManagement\n</code></pre>\n<p>This library will allow you to develop and expose application functionality based on features.\nIt's useful when you have special requirements when a new feature should be enabled and under what conditions.</p>\n<p>The next step is to register the required services with dependency injection by calling <code>AddFeatureManagement</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddFeatureManagement();\n</code></pre>\n<p>And you are ready to create your first feature flag.\nFeature flags are built on top of the .NET configuration system.\nAny .NET configuration provider can act as the backbone for exposing feature flags.</p>\n<p>Let's create a feature flag called <code>ClipArticleContent</code> in our <code>appsettings.json</code> file:</p>\n<pre><code class=\"language-json\">&quot;FeatureManagement&quot;: {\n  &quot;ClipArticleContent&quot;: false\n}\n</code></pre>\n<p>By convention, feature flags have to be defined in the <code>FeatureManagement</code> configuration section.\nBut you can change this by providing a different configuration section when calling <code>AddFeatureManagement</code>.</p>\n<p>Microsoft recommends exposing feature flags using enums and then consuming them with the <code>nameof</code> operator.\nFor example, you would write <code>nameof(FeatureFlags.ClipArticleContent)</code>.</p>\n<p>However, I prefer defining feature flags as constants in a static class because it simplifies the usage.</p>\n<pre><code class=\"language-csharp\">// Using enums\npublic enum FeatureFlags\n{\n    ClipArticleContent = 1\n}\n\n// Using constants\npublic static class FeatureFlags\n{\n    public const string ClipArticleContent = &quot;ClipArticleContent&quot;;\n}\n</code></pre>\n<p>To check the feature flag state, you can use the <code>IFeatureManager</code> service.\nIn this example, if the <code>ClipArticleContent</code> feature flag is turned on, we will return only the first thirty characters of the article's content.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;articles/{id}&quot;, async (\n    Guid id,\n    IGetArticle query,\n    IFeatureManager featureManager) =&gt;\n{\n    var article = query.Execute(id);\n\n    if (await featureManager.IsEnabledAsync(FeatureFlags.ClipArticleContent))\n    {\n        article.Content = article.Content.Substring(0, 50);\n    }\n\n    return Results.Ok(article);\n});\n</code></pre>\n<p>You can also apply feature flags on a controller or endpoint level using the <code>FeatureGate</code> attribute:</p>\n<pre><code class=\"language-csharp\">[FeatureGate(FeatureFlags.EnableArticlesApi)]\npublic class ArticlesController : Controller\n{\n   // ...\n}\n</code></pre>\n<p>This covers the fundamentals of using feature flags, and now, let's tackle more advanced topics.</p>\n<h2>Feature Filters And Phased Rollouts</h2>\n<p>The feature flags I showed you in the previous section were like a simple on-off switch.\nAlthough practical, you might want more flexibility from your feature flags.</p>\n<p>The <code>Microsoft.FeatureManagement</code> package comes with a few built-in feature filters that allow you to create dynamic rules for enabling feature flags.</p>\n<p>The available feature filters are\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.featurefilters.percentagefilter?view=azure-dotnet\"><code>Microsoft.Percentage</code></a>,\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.featurefilters.timewindowfilter?view=azure-dotnet\"><code>Microsoft.TimeWindow</code></a>\nand <a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.featurefilters.targetingfilter?view=azure-dotnet\"><code>Microsoft.Targeting</code></a>.</p>\n<p>Here's an example of defining a <code>ShowArticlePreview</code> feature flag that uses a percentage filter:</p>\n<pre><code class=\"language-json\">&quot;FeatureFlags&quot;: {\n  &quot;ClipArticleContent&quot;: false,\n  &quot;ShowArticlePreview&quot;: {\n    &quot;EnabledFor&quot;: [\n      {\n        &quot;Name&quot;: &quot;Percentage&quot;,\n        &quot;Parameters&quot;: {\n          &quot;Value&quot;: 50\n        }\n      }\n    ]\n  }\n}\n</code></pre>\n<p>This means the feature flag will be randomly turned on 50% of the time.\nThe downside is the same user might see different behavior on subsequent requests.\nA more realistic scenario is to have the feature flag state be cached for the duration of the user's session.</p>\n<p>To use the <code>PercentageFilter</code>, you need to enable it by calling <code>AddFeatureFilter</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddFeatureManagement().AddFeatureFilter&lt;PercentageFilter&gt;();\n</code></pre>\n<p>Another interesting feature filter is the <code>TargetingFilter</code>, which allows you to target specific users.\nTargeting is used in phased rollouts, where you want to introduce a new feature to your users gradually.\nYou start by enabling the feature for a small percentage of users and slowly increase the rollout percentage while monitoring how the system responds.</p>\n<h2>Trunk-based Development and Feature Flags</h2>\n<p><a href=\"https://trunkbaseddevelopment.com/\">Trunk-based development</a> is a Git branching strategy where all developers work in short-lived branches or directly in the trunk, the main codebase.\nThe <em>&quot;trunk&quot;</em> is the main branch of your repository.\nIf you're using Git, it will be either the <code>main</code> or <code>master</code> branch.\nTrunk-based development avoids the &quot;merge hell&quot; problem caused by long-lived branches.</p>\n<p>So, how do feature flags fit into trunk-based development?</p>\n<p>The only way to ensure the trunk is always releasable is to hide incomplete features behind feature flags.\nYou continue pushing changes to the trunk as you work on the feature while the feature flag remains turned off on the main branch.\nWhen the feature is complete, you turn on the feature flag and release it to production.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_055/trunk_based_development.png\" alt=\"Trunk based development.\">\n<h2>How I Used Feature Flags for A/B Testing On My Website</h2>\n<p>A/B testing (split testing) is an experiment where two or more variants of a page (or feature) are shown randomly to users.\nStatistical analysis is performed in the background to determine which variation performs better for a given conversion goal.</p>\n<p>Here's an example A/B test I performed on my website:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_055/split_test.png\" alt=\"Split test with two variants.\">\n<p>The hypothesis was that removing the image and focusing on the benefits would make more people want to subscribe.\nI measure this using the conversion rate, which is the number of people visiting the page divided by the number of people subscribing.</p>\n<p>I'm using a platform called <a href=\"https://posthog.com\">Posthog</a> to run experiments, which automatically calculates the results.</p>\n<p>You can see that the <em>test</em> variant has a significantly higher conversion rate, so it becomes the winner of the A/B test.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_055/experiment_results.png\" alt=\"Split test with two variants experiment results.\">\n<h2>Takeaway</h2>\n<p>The ability to dynamically turn features on or off without deploying the code is like a superpower.\nFeature flags give you this ability with very little work.</p>\n<p>You can work with feature flags in .NET by installing the <code>Microsoft.FeatureManagement</code> library.\nFeature flags build on top of the .NET configuration system, and you can check the feature flag state using the <code>IFeatureManager</code> service.</p>\n<p>Another use case for feature flags is A/B testing.\nI run weekly experiments on my website, testing changes that will improve my conversion rate.\nFeature flags help me decide which version of the website to show to the user.\nAnd then, I can measure results based on the user's actions.</p>\n<p>I also made a video about <a href=\"https://youtu.be/QVEUgIC7Wpo\"><strong>feature flagging in .NET,</strong></a> and you can watch it <a href=\"https://youtu.be/QVEUgIC7Wpo\"><strong>here</strong></a> if you want to learn more.</p>\n<p>Hope this was valuable.</p>\n<p>Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/feature-flags-in-dotnet-and-how-i-use-them-for-ab-testing",
            "title": "Feature Flags in .NET and How I Use Them for A/B Testing",
            "summary": "Feature flags let you wrap a feature in a conditional statement and toggle it on or off at runtime, without redeploying the code.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_055.png",
            "date_modified": "2023-09-16T00:00:00.000Z",
            "date_published": "2023-09-16T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking",
            "content_html": "<p>EF Core solves race conditions with optimistic concurrency, which takes no locks on the data.\nYou configure a property as a concurrency token, usually a SQL Server <code>rowversion</code> column, and EF Core adds it to the <code>WHERE</code> clause of the <code>UPDATE</code>.\nIf the value changed since you queried it, the save throws a <code>DbUpdateConcurrencyException</code>.</p>\n<p>How often do you think about concurrency conflicts when writing code?</p>\n<p>You write the code for a new feature, confirm that it works, and call it a day.</p>\n<p>But one week later, you find out you introduced a nasty bug because you didn't think about concurrency.</p>\n<p>The most common issue is race conditions with two competing threads executing the same function.\nIf you don't consider this during development, you introduce the risk of leaving the system in a corrupted state.</p>\n<p>In this week's newsletter, I'll challenge you to spot the race condition in a method for reserving a booking.\nThe business requirement is you can't have two overlapping reservations for the same dates.</p>\n<p>And then, I'll show you how to solve this race condition using EF Core optimistic concurrency.</p>\n<p>Let's dive in!</p>\n<h2>What's Wrong With This Code?</h2>\n<p>There's a race condition hiding somewhere in this code snippet.</p>\n<p>Can you see it?</p>\n<pre><code class=\"language-csharp\">public Result&lt;Guid&gt; Handle(\n    ReserveBooking command,\n    AppDbContext dbContext)\n{\n    var user = dbContext.Users.GetById(command.UserId);\n    var apartment = dbContext.Apartments.GetById(command.ApartmentId);\n    var (startDate, endDate) = command;\n\n    if (dbContext.Bookings.IsOverlapping(apartment, startDate, endDate))\n    {\n        return Result.Failure&lt;Guid&gt;(BookingErrors.Overlap);\n    }\n\n    var booking = Booking.Reserve(apartment, user, startDate, endDate);\n\n    dbContext.Add(booking);\n\n    dbContext.SaveChanges();\n\n    return booking.Id;\n}\n</code></pre>\n<p>The call to <code>IsOverlapping</code> is an optimistic check to see if there's an existing booking for the specified dates.</p>\n<pre><code class=\"language-csharp\">if (dbContext.Bookings.IsOverlapping(apartment, startDate, endDate)) { }\n</code></pre>\n<p>If it returns <code>true</code>, we're trying to double-book the apartment.\nSo we return a failure, and the method completes.</p>\n<p>But if it returns <code>false</code>, we reserve a booking and call <code>SaveChanges</code> to persist the changes in the database.</p>\n<p>And there lies the problem.</p>\n<p>There's a chance for a concurrent request to pass the <code>IsOverlapping</code> check and attempt to reserve the booking.\nWithout any concurrency control, both requests will succeed, and we will end up with an inconsistent state in the database.</p>\n<p>So how can we solve this?</p>\n<h2>Optimistic Concurrency With EF Core</h2>\n<p>The <a href=\"https://milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core\"><strong>pessimistic concurrency</strong></a> approach acquires a lock for the data before modifying it.\nIt's slower and causes competing transactions to be blocked until the lock is released.\nEF Core doesn't support this approach out of the box.</p>\n<p>You can also solve this problem using optimistic concurrency with EF Core.\nIt doesn't take any locks, but any data modifications will fail to save if the data has changed since it was queried.</p>\n<p>To implement optimistic concurrency in EF Core, you need to configure a property as a <em>concurrency token</em>.\nIt's loaded and tracked with the entity.\nWhen you call <code>SaveChanges</code>, EF Core will compare the value of the concurrency token to the value in the database.</p>\n<p>Let's assume we're using SQL Server, which has a native <a href=\"https://learn.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-ver16\"><code>rowversion</code></a> column.\nThe <code>rowversion</code> automatically changes when the row is updated, so it's a great option for a concurrency token.</p>\n<p>To configure a <code>byte[]</code> property as a concurrency token you can decorate it with the <code>Timestamp</code> attribute.\nIt will be mapped to a <code>rowversion</code> column in SQL Server.</p>\n<pre><code class=\"language-csharp\">public class Apartment\n{\n    public Guid Id { get; set; }\n\n    [Timestamp]\n    public byte[] Version { get; set; }\n}\n</code></pre>\n<p>I prefer a different approach because attributes pollute the entity.</p>\n<p>You can do the same with the Fluent API.\nI will even use a shadow property to hide the concurrency token from the entity class.</p>\n<pre><code class=\"language-csharp\">protected override void OnModelCreating(ModelBuilder modelBuilder)\n{\n    modelBuilder.Entity&lt;Apartment&gt;()\n        .Property&lt;byte[]&gt;(&quot;Version&quot;)\n        .IsRowVersion();\n}\n</code></pre>\n<p>The exact configuration will differ based on the database you are using, so check the documentation.</p>\n<h2>How Optimistic Concurrency Works In Practice</h2>\n<p>So here's what changes when we configure the concurrency token.</p>\n<p>When loading the <code>Apartment</code> entity, EF will also load the concurrency token.</p>\n<pre><code class=\"language-sql\">SELECT a.Id, a.Version\nFROM Apartments a\nWHERE a.Id = @p0\n</code></pre>\n<p>And when we call <code>SaveChanges</code>, the update statement will compare the concurrency token value with the one in the database:</p>\n<pre><code class=\"language-sql\">UPDATE Apartments a\nSET a.LastBookedOnUtc = @p0\nWHERE a.Id = @p1 AND a.Version = @p2;\n</code></pre>\n<p>If the <code>rowversion</code> in the database changes, the number of updated rows will be <code>0</code>.</p>\n<p>EF Core expects to update <code>1</code> row, so it will throw a <code>DbUpdateConcurrencyException</code>, which you need to handle.</p>\n<h2>Handling Concurrency Exceptions</h2>\n<p>Now that you know how to use optimistic concurrency with EF Core, you can fix the previous code snippet.</p>\n<p>If two concurrent requests pass the <code>IsOverlapping</code> check, only one can complete the <code>SaveChanges</code> call.\nThe other concurrent request will run into a <code>Version</code> mismatch in the database and throw a <code>DbUpdateConcurrencyException</code>.</p>\n<p>In case of a concurrency conflict, we need to add a <code>try-catch</code> statement to catch the <code>DbUpdateConcurrencyException</code>.\nHow you handle the actual exception depends on your business requirements.\nAnd sometimes, <a href=\"https://go.particular.net/milanjovanovic/raceconditions\">race conditions</a> might not even exist.</p>\n<pre><code class=\"language-csharp\">public Result&lt;Guid&gt; Handle(\n    ReserveBooking command,\n    AppDbContext dbContext)\n{\n    var user = dbContext.Users.GetById(command.UserId);\n    var apartment = dbContext.Apartments.GetById(command.ApartmentId);\n    var (startDate, endDate) = command;\n\n    if (dbContext.Bookings.IsOverlapping(apartment, startDate, endDate))\n    {\n        return Result.Failure&lt;Guid&gt;(BookingErrors.Overlap);\n    }\n\n    try\n    {\n        var booking = Booking.Reserve(apartment, user, startDate, endDate);\n\n        dbContext.Add(booking);\n\n        dbContext.SaveChanges();\n\n        return booking.Id;\n    }\n    catch (DbUpdateConcurrencyException)\n    {\n        return Result.Failure&lt;Guid&gt;(BookingErrors.Overlap);\n    }\n}\n</code></pre>\n<p>If you're wondering how will this even work, here's the missing piece.\nThe <code>Booking.Reserve</code> method will update the <code>LastBookedOnUtc</code> property of the <code>Apartment</code> entity.</p>\n<pre><code class=\"language-csharp\">public static Booking Reserve(Apartment apartment, User user, DateTime startDate, DateTime endDate)\n{\n    apartment.LastBookedOnUtc = DateTime.UtcNow;\n\n    return new Booking\n    {\n        Id = Guid.NewGuid(),\n        Apartment = apartment,\n        User = user,\n        StartDate = startDate,\n        EndDate = endDate\n    };\n}\n</code></pre>\n<p>When we call <code>SaveChanges</code>, this will cause an update to the <code>Apartment</code> entity, which will include the concurrency token check.\nThis ensures that if two requests try to update the same entity simultaneously, one will succeed while the other will fail with a <code>DbUpdateConcurrencyException</code>.</p>\n<h2>When Should You Use Optimistic Concurrency?</h2>\n<p>Optimistic concurrency considers the best scenario is also the most probable one.\nIt assumes conflicts between transactions will be infrequent and doesn't acquire locks on the data.\nThis means your system can scale better because there is no blocking slowing down performance.</p>\n<p>However, you must still expect <a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6\"><strong>concurrency conflicts</strong></a> and implement custom logic to handle them.</p>\n<p>Optimistic concurrency is a good choice if your application doesn't expect many conflicts.</p>\n<p>Another reason to use optimistic concurrency is when you can't hold an open connection to the database for the length of the transaction.\nThis is required for pessimistic locking.</p>\n<p>Hope this was helpful.</p>\n<p>I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking",
            "title": "Solving Race Conditions With EF Core Optimistic Locking",
            "summary": "I'll challenge you to spot the race condition in a method for reserving a booking, where you can't have two overlapping reservations for the same dates.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_054.png",
            "date_modified": "2023-09-09T00:00:00.000Z",
            "date_published": "2023-09-09T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet",
            "content_html": "<p>Testcontainers is a .NET library that spins up throwaway Docker containers for integration tests.\nInstead of mocks or in-memory databases, you run the real SQL Server image, point EF Core at its connection string, and dispose the container when the tests finish.\nIt works in any CI pipeline that supports Docker.</p>\n<p>Modern software applications rarely work in isolation.\nOn the contrary, a typical application will talk to several external systems like databases, messaging systems, cache providers, and many 3rd party services.\nAnd it's up to you to ensure everything functions correctly.</p>\n<p>Hopefully, I don't have to convince you about the value of writing tests.</p>\n<p>You should be writing tests.\nPeriod.</p>\n<p>However, I do want to discuss the <em>value</em> of <strong>integration testing</strong>.</p>\n<p><a href=\"https://milanjovanovic.tech/blog/unit-testing-best-practices-dotnet\"><strong>Unit tests</strong></a> are helpful to test business logic in isolation, without any external services.\nThey are easy to write and provide almost instant feedback.</p>\n<p>But you can't be fully confident in your application without <strong>integration tests</strong>.</p>\n<p>So, in this week's newsletter, I'll show you how to use <strong>Docker</strong> for integration testing.</p>\n<p>Here's what we will use to write <strong>integration tests</strong>:</p>\n<ul>\n<li><strong>Testcontainers</strong></li>\n<li>Docker</li>\n<li><a href=\"https://milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit\"><strong>xUnit</strong></a></li>\n</ul>\n<p>Let's dive in!</p>\n<h2>What is Testcontainers?</h2>\n<p><a href=\"https://dotnet.testcontainers.org/\">Testcontainers</a> is a library for writing tests with throwaway Docker containers.</p>\n<p>Why should you use it?</p>\n<p>Integration testing is considered &quot;difficult&quot; because you have to maintain testing infrastructure.\nBefore running tests, you need to make sure the database is up and running.\nYou also have to seed any data required for the tests.\nIf you have tests running in parallel on the same database, they could interfere with each other.</p>\n<p>A possible solution could be using in-memory variations of the required services.\nBut this isn't much different from using mocks.\nIn-memory services might not have all the features of the production service.</p>\n<p>Testcontainers solves this by using Docker to spin up real services for integration testing.</p>\n<p>Here's an example of creating a <strong>SQL Server</strong> container:</p>\n<pre><code class=\"language-csharp\">MsSqlContainer dbContainer = new MsSqlBuilder()\n    .WithImage(&quot;mcr.microsoft.com/mssql/server:2022-latest&quot;)\n    .WithPassword(&quot;Strong_password_123!&quot;)\n    .Build();\n</code></pre>\n<p>You can then use the <code>MsSqlContainer</code> instance to get a connection string for the database running inside the container.</p>\n<p>Do you see how this is valuable for writing integration tests?</p>\n<p>No more need for mocks or fake in-memory databases.\nInstead, you can use the real deal.</p>\n<p>I won't do a deep dive into this library here, so refer to the documentation for more information.</p>\n<h2>Implementing a Custom WebApplicationFactory</h2>\n<p><strong>ASP.NET Core</strong> provides an in-memory test server that we can use to spin up an application instance for running tests.\nThe <code>Microsoft.AspNetCore.Mvc.Testing</code> package provides the <code>WebApplicationFactory</code> class that we will use as the base for our implementation.</p>\n<p><code>WebApplicationFactory&lt;TEntryPoint&gt;</code> is used to create a <code>TestServer</code> for the integration tests.</p>\n<p>The custom <code>IntegrationTestWebAppFactory</code> will do a few things:</p>\n<ul>\n<li>Create and configure a <code>MsSqlContainer</code> instance</li>\n<li>Call <code>ConfigureTestServices</code> to set up EF Core with the container database</li>\n<li>Start and stop the container instance with <code>IAsyncLifetime</code></li>\n</ul>\n<p><code>MsSqlContainer</code> has a <code>GetConnectionString</code> method to grab the connection string for the current container.\nNote that this can change between tests, as each test class will create a separate container instance.\nTest cases inside the same test class will use the same container instance.\nSo keep that in mind if you need to do a cleanup between tests.</p>\n<p>Another thing to keep in mind is <strong>database migrations</strong>.\nYou will have to run them manually before every test to create the required database structure.</p>\n<p>Starting the container instance is done asynchronously using <code>IAsyncLifetime</code>.\nThe container is started inside <code>StartAsync</code> before any of the tests run.\nAnd it's stopped inside <code>StopAsync</code>.</p>\n<p>Here's the complete code for <code>IntegrationTestWebAppFactory</code>:</p>\n<pre><code class=\"language-csharp\">public class IntegrationTestWebAppFactory\n    : WebApplicationFactory&lt;Program&gt;,\n      IAsyncLifetime\n{\n    private readonly MsSqlContainer _dbContainer = new MsSqlBuilder()\n        .WithImage(&quot;mcr.microsoft.com/mssql/server:2022-latest&quot;)\n        .WithPassword(&quot;Strong_password_123!&quot;)\n        .Build();\n\n    protected override void ConfigureWebHost(IWebHostBuilder builder)\n    {\n        builder.ConfigureTestServices(services =&gt;\n        {\n            var descriptorType =\n                typeof(DbContextOptions&lt;ApplicationDbContext&gt;);\n\n            var descriptor = services\n                .SingleOrDefault(s =&gt; s.ServiceType == descriptorType);\n\n            if (descriptor is not null)\n            {\n                services.Remove(descriptor);\n            }\n\n            services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;\n                options.UseSqlServer(_dbContainer.GetConnectionString()));\n        });\n    }\n\n    public Task InitializeAsync()\n    {\n        return _dbContainer.StartAsync();\n    }\n\n    public new Task DisposeAsync()\n    {\n        return _dbContainer.StopAsync();\n    }\n}\n</code></pre>\n<h2>Creating The Base Test Class</h2>\n<p>The base test class will implement a class fixture interface <code>IClassFixture</code>.\nIt indicates the class contains tests and provides shared object instances across the test cases inside.\nThis is a good place to instantiate any services that are required for most tests.</p>\n<p>For example, I'm creating an <code>IServiceScope</code> for resolving scoped services inside the tests.</p>\n<ul>\n<li><code>ISender</code> for sending commands and queries</li>\n<li><code>ApplicationDbContext</code> for database setup or verifying results</li>\n</ul>\n<pre><code class=\"language-csharp\">public abstract class BaseIntegrationTest\n    : IClassFixture&lt;IntegrationTestWebAppFactory&gt;,\n      IDisposable\n{\n    private readonly IServiceScope _scope;\n    protected readonly ISender Sender;\n    protected readonly ApplicationDbContext DbContext;\n\n    protected BaseIntegrationTest(IntegrationTestWebAppFactory factory)\n    {\n        _scope = factory.Services.CreateScope();\n\n        Sender = _scope.ServiceProvider.GetRequiredService&lt;ISender&gt;();\n\n        DbContext = _scope.ServiceProvider\n            .GetRequiredService&lt;ApplicationDbContext&gt;();\n    }\n\n    public void Dispose()\n    {\n        _scope?.Dispose();\n        DbContext?.Dispose();\n    }\n}\n</code></pre>\n<p>With all the infrastructure in place, we're finally ready to write the tests.</p>\n<h2>Putting It All Together - Writing Integration Tests</h2>\n<p>Here's a <code>ProductTests</code> class with an integration test inside.</p>\n<p>I use the <em>Arrange-Act-Assert</em> pattern to structure tests:</p>\n<ul>\n<li><em>Arrange</em> - create the <code>CreateProduct.Command</code> instance</li>\n<li><em>Act</em> - send the command using <code>ISender</code> and store the result</li>\n<li><em>Assert</em> - use the result from the <em>Act</em> step to verify the database state</li>\n</ul>\n<p>The value of writing integration tests like this is that you can use the complete <strong>MediatR</strong> request pipeline.\nIf you have any <code>IPipelineBehavior</code> wrapping the request, it will also be executed.</p>\n<p>The same applies if you write your business logic inside service classes.\nInstead of resolving the <code>ISender</code>, you would resolve the specific services you want to test.</p>\n<p>Most importantly, this test uses a real database instance running inside a <a href=\"https://milanjovanovic.tech/blog/docker-dotnet-developers\"><strong>Docker container</strong></a>.</p>\n<pre><code class=\"language-csharp\">public class ProductTests : BaseIntegrationTest\n{\n    public ProductTests(IntegrationTestWebAppFactory factory)\n        : base(factory)\n    {\n    }\n\n    [Fact]\n    public async Task Create_ShouldCreateProduct()\n    {\n        // Arrange\n        var command = new CreateProduct.Command\n        {\n            Name = &quot;AMD Ryzen 7 7700X&quot;,\n            Category = &quot;CPU&quot;,\n            Price = 223.99m\n        };\n\n        // Act\n        var productId = await Sender.Send(command);\n\n        // Assert\n        var product = DbContext\n            .Products\n            .FirstOrDefault(p =&gt; p.Id == productId);\n\n        Assert.NotNull(product);\n    }\n}\n</code></pre>\n<h2>Running Integration Tests In CI/CD Pipelines</h2>\n<p>You can also run integration tests with <strong>Testcontainers</strong> inside <strong>CI/CD pipelines</strong>.\nThe only requirement is that it supports Docker.</p>\n<p><strong>GitHub Actions</strong> does support Docker.\nIf you are hosting your project there, integration tests will work out of the box.</p>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet\"><strong>building a CI/CD pipeline with GitHub Actions here.</strong></a></p>\n<p>And if you want a plug-in solution, here's a GitHub Actions workflow you can use:</p>\n<pre><code class=\"language-yaml\">name: Run Tests 🚀\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n\njobs:\n  run-tests:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: '7.0.x'\n\n      - name: Restore\n        run: dotnet restore ./Products.Api.sln\n\n      - name: Build\n        run: dotnet build ./Products.Api.sln --no-restore\n\n      - name: Test\n        run: dotnet test ./Products.Api.sln --no-build\n</code></pre>\n<h2>Takeaway</h2>\n<p><strong>Testcontainers</strong> is an excellent solution for writing <strong>integration tests</strong> with Docker.\nYou can spin up and configure any <strong>Docker</strong> image and use it from your application.\nThis is far better than using mocks or in-memory variations because they lack many features.</p>\n<p>If you have a CI/CD pipeline that supports Docker, Testcontainers will work out of the box.</p>\n<p>Only a few integration tests will drastically improve your confidence in the system.</p>\n<p>You can grab the <a href=\"https://github.com/m-jovanovic/testcontainers-sample\"><strong>source code for this newsletter</strong></a> on my GitHub.<br>\nIt's completely free, so what are you waiting for?</p>\n<p>And if you prefer video, here's a quick tutorial on <a href=\"https://youtu.be/tj5ZCtvgXKY\"><strong>integration testing with Testcontainers.</strong></a></p>\n<p>Hope this was valuable.</p>\n<p>Stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet",
            "title": "Testcontainers - Integration Testing Using Docker In .NET",
            "summary": "Unit tests are helpful, but you can't be fully confident in your application without integration tests.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_053.png",
            "date_modified": "2023-09-02T00:00:00.000Z",
            "date_published": "2023-09-02T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/orchestration-vs-choreography",
            "content_html": "<p>Orchestration is centralized and command-driven: one service acts as the orchestrator and tells the other services what to do.\nChoreography is decentralized and event-driven: each service publishes events, and other services react to the ones they care about.\nOrchestration is easier to monitor and troubleshoot, while choreography gives you looser coupling.</p>\n<p>More than 63%+ of organizations said in a <a href=\"https://dzone.com/articles/new-research-shows-63-percent-of-enterprises-are-a\">Dzone survey</a>\nthat they are adopting <strong>Microservices</strong> for some or all of their applications.</p>\n<p>As more businesses adopt the use of Microservice architectures, we as developers have to become more skilled with Microservices communication.</p>\n<p>Working with <strong>distributed systems</strong> is both fun and challenging at the same time.\nOne of those challenges is designing <strong>effective communication</strong> between services.</p>\n<p>More centralization or less centralization?\nMore coupling or less coupling?\nMore control or less control?</p>\n<p>These are only a few questions you need to answer.</p>\n<p>In this week's newsletter, we will:</p>\n<ul>\n<li>Break down <strong>Orchestration vs. Choreography</strong></li>\n<li>Understand key differences and <strong>tradeoffs</strong> between them</li>\n<li>Create a framework for deciding which approach to use</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>What Are Microservices?</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>Microservices</strong></a> are a <strong>software architecture</strong> style where an application is built from small, autonomous services.\nEach microservice serves a distinct purpose and can be independently deployed.</p>\n<p>You probably already know this, so this is a quick refresher.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_052/microservices_hell.png\" alt=\"Complex dependency graphs for Amazon and Netflix microservice architectures\">\n<p>Here are the key tenets of Microservices:</p>\n<ul>\n<li>Independent development</li>\n<li>Deployment independence</li>\n<li>Technological freedom</li>\n<li>Scalability</li>\n<li>Resilience</li>\n</ul>\n<p>A significant challenge is designing effective inter-service communication within this distributed environment.</p>\n<p>Inside a Monolith system, communication happens through direct method calls.\nThis is a straightforward approach that works well when all components live within a single process.\nHowever, this doesn't work with microservices.</p>\n<h2>Orchestration - Command-driven Communication</h2>\n<p><strong>Orchestration</strong> is a centralized approach to Microservices communication.\nOne of the services takes on the role of the <strong>orchestrator</strong> and coordinates the communication between services.</p>\n<p>Orchestration uses <strong>command-driven</strong> communication.\nThe command communicates the intent of the action.\nThe sender wants something to happen, and the recipient doesn't need to know who sent the command.</p>\n<p>An example of orchestration can be a <a href=\"https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq\"><strong>Saga implemented with RabbitMQ.</strong></a></p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_052/orchestration.png\" alt=\"Orchestration flow where Ordering commands Inventory, Payment, Notification, and Shipping services\">\n<p>It has some nice <strong>benefits</strong>:</p>\n<ul>\n<li>Simple</li>\n<li>Centralized</li>\n<li>Ease of troubleshooting</li>\n<li>Monitoring is straightforward</li>\n</ul>\n<p><strong>Orchestration</strong> is typically <strong>simpler</strong> to implement and maintain than choreography.\nBecause there is a central coordinator, you can manage and monitor service interactions.\nThis, in turn, improves troubleshooting, as you know where to look when something goes wrong.</p>\n<p>The <strong>drawbacks</strong> of orchestration are:</p>\n<ul>\n<li>Tight coupling</li>\n<li>Single point of failure</li>\n<li>Difficulty adding, removing, or replacing microservices</li>\n</ul>\n<h2>Choreography - Event-driven Communication</h2>\n<p><strong>Choreography</strong> is a <strong>decentralized</strong> communication approach.\nChoreography uses <a href=\"https://milanjovanovic.tech/blog/event-driven-architecture-in-dotnet-with-rabbitmq\"><strong>event-driven</strong></a> communication - as opposed to orchestration, which uses commands.</p>\n<p>An <strong>event</strong> is something that has happened in the past and is a fact.\nThe sender does not know who will handle the event or what will happen after processing it.</p>\n<p>I talked about events in-depth in the newsletter about <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems\"><strong>publishing domain events.</strong></a></p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_052/choreography.png\" alt=\"Choreography flow where services exchange order events through a message broker\">\n<p>The most important <strong>benefits</strong> of choreography are:</p>\n<ul>\n<li>Loose coupling</li>\n<li>Ease of maintenance</li>\n<li>Decentralized control</li>\n<li>Asynchronous communication</li>\n</ul>\n<p><strong>Choreography</strong> allows microservices to be <strong>loosely coupled</strong>, which means they can operate independently and asynchronously.\nThis makes the system more scalable and resilient.\nA failure of one microservice won't necessarily affect microservices.</p>\n<p>Of course, there are <strong>downsides</strong> to choreography:</p>\n<ul>\n<li>Complexity</li>\n<li>Monitoring is difficult</li>\n<li>Difficulty troubleshooting</li>\n</ul>\n<p>It's more complex to implement and maintain than orchestration.</p>\n<p>Effective monitoring is the biggest challenge from my experience.</p>\n<h2>The Crossroads: Which One Should You Choose?</h2>\n<p>So, how do you know which one to pick for your system?</p>\n<p>I always start with the requirements of the system I'm building.\nAnd then, I look at the pros and cons of orchestration vs. choreography.\nHere's a small framework to help you decide.</p>\n<p><strong>Orchestration</strong> excels when:</p>\n<ul>\n<li>You need to wait for the completion of intermediate steps (such as credit card payment confirmation)</li>\n<li>You need to make a conditional choice of subsequent steps</li>\n<li>The process must be carried out atomically (entirely or not at all)</li>\n<li>The process needs to be centralized in one place for monitoring</li>\n</ul>\n<p>This also means you will need a central database managed by the orchestrator to handle all workflow-related state.</p>\n<p><strong>Choreography</strong> works best when:</p>\n<ul>\n<li>The process can rely on the input message without needing additional context</li>\n<li>Steps clearly follow one another</li>\n<li>Progress is made in one direction</li>\n</ul>\n<p>You can benefit from increased flexibility (such as modifying individual steps in isolation)\nUnfortunately, choreography can make it difficult to trace, debug, or monitor the processes triggered by an event.\nAnd the larger the event stream is, the more challenging it becomes.</p>\n<p>So, think carefully before opting for orchestration or choreography.</p>\n<p>Both approaches bring their advantages but also downsides.</p>\n<h2>Takeaway</h2>\n<p><strong>Orchestration</strong> defines a sequence of steps that each microservice must follow.\nThis is great for identifying and addressing complex service interdependencies.\nAnother benefit is that business logic can be managed and monitored in one place.</p>\n<p><strong>Choreography</strong>, on the other hand, is a decentralized technique for microservices communication.\nEach service can operate independently while still being part of the larger architecture.</p>\n<p>To decide which approach to use, you should observe your system and identify what you stand to gain or lose.\nEverything is a tradeoff.</p>\n<p>There's also an <strong>alternative approach</strong> I want to mention.</p>\n<p>You could go for a <strong>hybrid approach</strong> that integrates orchestration and choreography.</p>\n<p>In a <strong>hybrid approach</strong>, you decide which communication technique to use for a specific workflow.\nSome workflows can benefit from orchestration, and others can benefit more from choreography.</p>\n<p>Hope this was helpful.</p>\n<p>I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/orchestration-vs-choreography",
            "title": "Orchestration vs Choreography",
            "summary": "Designing effective communication between services is one of the challenges of working with distributed systems. More centralization or less?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_052.png",
            "date_modified": "2023-08-26T00:00:00.000Z",
            "date_published": "2023-08-26T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/advanced-rate-limiting-use-cases-in-dotnet",
            "content_html": "<p>The built-in .NET rate limiter goes beyond a single global policy once you partition it.\nA <code>RateLimitPartition</code> lets you key the limit on the client's IP address for anonymous users, or on the user identity for authenticated ones.\nIf you run behind a reverse proxy, you partition on the <code>X-Forwarded-For</code> header instead of the connection IP, or apply rate limiting at the proxy itself.</p>\n<p><strong>Rate limiting</strong> is about restricting the number of requests to your application.\nIt's usually applied within a specific time window or based on other criteria.</p>\n<p>It's helpful for a few reasons:</p>\n<ul>\n<li>Improves security</li>\n<li>Guards against DDoS attacks</li>\n<li>Prevents overloading of application servers</li>\n<li>Reduces costs by preventing unnecessary resource consumption</li>\n</ul>\n<p><strong>.NET 7</strong> shipped with a <strong>built-in rate limiter</strong>, but you need to know how to implement it correctly.\nOr you could grind your system to a halt - and we don't want that.</p>\n<p>In this week's newsletter, I'll teach you:</p>\n<ul>\n<li>How to rate limit users by <strong>IP address</strong></li>\n<li>How to rate limit users by their <strong>identity</strong></li>\n<li>How to apply <strong>rate limiting</strong> on the <strong>reverse proxy</strong></li>\n</ul>\n<p>So let's dive in!</p>\n<h2>Built-In Rate Limiting In .NET 7</h2>\n<p>Starting with .NET 7, we have access to built-in <strong>rate limiting middleware</strong> in the <code>Microsoft.AspNetCore.RateLimiting</code> namespace.\nThe API is straightforward, and you can create a rate limit policy with a few lines of code.</p>\n<p>We can use one of the four <strong>rate limiting algorithms</strong>:</p>\n<ul>\n<li>Fixed window</li>\n<li>Sliding window</li>\n<li>Token bucket</li>\n<li>Concurrency</li>\n</ul>\n<p>Here's how to define a <strong>rate limit policy</strong> by calling the <code>AddTokenBucketLimiter</code> method:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;\n\n    rateLimiterOptions.AddTokenBucketLimiter(&quot;token&quot;, options =&gt;\n    {\n        options.TokenLimit = 1000;\n        options.ReplenishmentPeriod = TimeSpan.FromHours(1);\n        options.TokensPerPeriod = 700;\n        options.AutoReplenishment = true;\n    });\n});\n</code></pre>\n<p>Now you can reference the <code>token</code> rate limit policy on your endpoint or controller.</p>\n<p>You also have to add the <code>RateLimitingMiddleware</code> to the request pipeline:</p>\n<pre><code class=\"language-csharp\">app.UseRateLimiter();\n</code></pre>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core\"><strong>rate limiting in .NET 7 here,</strong></a> so I won't go deeper into the fundamentals.</p>\n<h2>Rate Limiting Users By IP Address</h2>\n<p>The approach I just showed you has a <strong>problem</strong> - the <strong>rate limit policy</strong> is global and <strong>applies to all users</strong>.</p>\n<p>Most of the time, you don't want to do this.\nRate limiting should be granular and apply to <strong>individual users</strong>.</p>\n<p>Luckily, you can achieve this by creating a <code>RateLimitPartition</code>.</p>\n<p>The <code>RateLimitPartition</code> has two components:</p>\n<ul>\n<li>Partition key</li>\n<li>Rate limiter policy</li>\n</ul>\n<p>Here's how to define a rate limiter with a fixed window policy, and the <strong>partition key</strong> is the user's <strong>IP address</strong>.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(options =&gt;\n{\n    options.AddPolicy(&quot;fixed-by-ip&quot;, httpContext =&gt;\n        RateLimitPartition.GetFixedWindowLimiter(\n            partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),\n            factory: _ =&gt; new FixedWindowRateLimiterOptions\n            {\n                PermitLimit = 10,\n                Window = TimeSpan.FromMinutes(1)\n            }));\n});\n</code></pre>\n<p>Rate limiting by <strong>IP address</strong> can be a good layer of security for <strong>unauthenticated users</strong>.\nYou don't know who is accessing your system and can't apply more granular rate limiting.\nThis can help protect your system from malicious users trying to perform a DDoS attack.</p>\n<p>You can also <a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-7.0#create-chained-limiters\"><strong>create chained limiters</strong></a> using the <code>CreateChained</code> API.\nIt allows you to pass in multiple <code>PartitionedRateLimiter</code>, which are combined into one <code>PartitionedRateLimiter</code>.\nThe chained limiter runs all the input limiters in sequence (one by one).</p>\n<p>If your application is running behind a <strong>reverse proxy</strong>, you need to make sure not to rate limit the proxy IP address.\nReverse proxies usually <strong>forward</strong> the original IP address with the <code>X-Forwarded-For</code> header.\nSo you can use it as the <strong>partition key</strong>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(options =&gt;\n{\n    options.AddPolicy(&quot;fixed-by-ip&quot;, httpContext =&gt;\n        RateLimitPartition.GetFixedWindowLimiter(\n            httpContext.Request.Headers[&quot;X-Forwarded-For&quot;].ToString(),\n            factory: _ =&gt; new FixedWindowRateLimiterOptions\n            {\n                PermitLimit = 10,\n                Window = TimeSpan.FromMinutes(1)\n            }));\n});\n</code></pre>\n<h2>Rate Limiting Users By Identity</h2>\n<p>If you require users to <strong>authenticate</strong> with your API, you can determine who the current is.\nThen you can use the user's <strong>identity</strong> as the <strong>partition key</strong> for a <code>RateLimitPartition</code>.</p>\n<p>Here's how you would create such a rate limit policy:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(options =&gt;\n{\n    options.AddPolicy(&quot;fixed-by-user&quot;, httpContext =&gt;\n        RateLimitPartition.GetFixedWindowLimiter(\n            partitionKey: httpContext.User.Identity?.Name?.ToString(),\n            factory: _ =&gt; new FixedWindowRateLimiterOptions\n            {\n                PermitLimit = 10,\n                Window = TimeSpan.FromMinutes(1)\n            }));\n});\n</code></pre>\n<p>I'm using the <code>User.Identity</code> value on the <code>HttpContext</code> to get the current user's <code>Name</code> claim.\nThis usually corresponds to the <code>sub</code> claim inside a JWT - which is the user identifier.</p>\n<h2>Applying Rate Limting On The Reverse Proxy</h2>\n<p>In a robust implementation, you want to <strong>rate limit</strong> on the <strong>reverse proxy</strong> level before the request hits your API.\nAnd if you have a distributed system, this is a requirement.\nOtherwise, your system wouldn't function correctly.</p>\n<p>There are many reverse proxy implementations to choose from.</p>\n<p><strong>YARP</strong> is a reverse proxy with excellent .NET integration.\nNot surprising since it was written in C#.\nYou can learn more about <a href=\"https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp\"><strong>building an API Gateway with YARP here.</strong></a></p>\n<p>To implement rate limiting on the reverse proxy with <strong>YARP</strong> you need to:</p>\n<ul>\n<li>Define a rate limit policy (covered in previous examples)</li>\n<li>Configure the <code>RateLimiterPolicy</code> for the route in YARP settings</li>\n</ul>\n<pre><code class=\"language-json\">&quot;products-route&quot;: {\n  &quot;ClusterId&quot;: &quot;products-cluster&quot;,\n  &quot;RateLimiterPolicy&quot;: &quot;sixty-per-minute-fixed&quot;,\n  &quot;Match&quot;: {\n    &quot;Path&quot;: &quot;/products/{**catch-all}&quot;\n  },\n  &quot;Transforms&quot;: [\n    { &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }\n  ]\n}\n</code></pre>\n<p>The built-in rate limiter middleware uses an <strong>in-memory</strong> store to track the number of requests.\nIf you want to run your reverse proxy in a high-availability setup, you will need to use a <strong>distributed cache</strong>.\nA nice option to look into is using a <a href=\"https://github.com/cristipufu/aspnetcore-redis-rate-limiting\"><strong>Redis backplane for rate limiting.</strong></a></p>\n<h2>Closing Thoughts</h2>\n<p>With the <code>PartitionedRateLimiter</code> you can easily create granular rate limit policies.</p>\n<p>The two common approaches are:</p>\n<ul>\n<li>Rate limiting by <strong>IP address</strong></li>\n<li>Rate limiting by the <strong>user identifier</strong></li>\n</ul>\n<p>I was really excited to see the .NET team ship rate limiting.\nBut, the current implementation has its shortcomings.\nThe main issue is that it only works <strong>in memory</strong>.\nFor a <strong>distributed</strong> solution, you need to implement something yourself or use an external library.</p>\n<p>You can use the <strong>YARP</strong> reverse proxy to build robust and scalable distributed systems.\nAnd it only takes a few lines of code to add <strong>rate limiting</strong> on the reverse proxy level.\nI'm using it extensively in my systems.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/advanced-rate-limiting-use-cases-in-dotnet",
            "title": "Advanced Rate Limiting Use Cases In .NET",
            "summary": "Rate limiting is about restricting the number of requests to your application. It improves security, guards against DDoS attacks, and prevents overloading your…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_051.png",
            "date_modified": "2023-08-19T00:00:00.000Z",
            "date_published": "2023-08-19T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/mastering-dapper-relationship-mappings",
            "content_html": "<p>Dapper maps a flat result set straight into an object, but relationships need multi-mapping.\nFor a one-to-one relationship, pass both types to <code>QueryAsync</code>, supply a mapping function, and set <code>splitOn</code> to the first column of the nested object.\nFor one-to-many, keep a dictionary so the joined rows reuse the same parent, and many-to-many needs one dictionary per side.</p>\n<p><strong>Dapper</strong> is a lightweight <strong>object-relational mapper</strong> in .NET.\nIt's popular because it's easy to use and fast at the same time.</p>\n<p>Dapper extends the <code>IDbConnection</code> interface with methods for sending SQL queries to the database.</p>\n<p>But, because of the nature of SQL, mapping the result into an object model can be tricky.</p>\n<p>So in this week's newsletter, I'll teach you how to map:</p>\n<ul>\n<li>Simple queries</li>\n<li>One-to-one relationships</li>\n<li>One-to-many relationships</li>\n<li>Many-to-many relationships</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>Simple Mapping</h2>\n<p>Let's first see how to do a <strong>simple mapping</strong> using Dapper.</p>\n<p>Writing a query with Dapper has three parts:</p>\n<ul>\n<li>Creating an <code>IDbConnection</code> instance</li>\n<li>Writing the SQL query</li>\n<li>Calling a method that Dapper exposes</li>\n</ul>\n<p>We will write a SQL query to load a set of <code>LineItem</code> objects for a specific <code>Order</code>.</p>\n<pre><code class=\"language-csharp\">public class LineItem\n{\n    public long LineItemId { get; init; }\n\n    public long OrderId { get; init; }\n\n    public decimal Price { get; init; }\n\n    public string Currency { get; init; }\n\n    public decimal Quantity { get; init; }\n}\n</code></pre>\n<p>Here's the SQL query returning the result we need:</p>\n<pre><code class=\"language-sql\">SELECT Id AS LineItemId, OrderId, Price, Currency, Quantity\nFROM LineItems\nWHERE OrderId = @OrderId\n</code></pre>\n<p>I'm parameterizing the <code>Order</code> identifier using the <code>@OrderId</code> syntax.\nThis is a Dapper convention.\nIt's important that you use <strong>parameterized queries</strong> to <strong>avoid SQL injection attacks</strong>.</p>\n<p>The mapping is straightforward in this case because we are only returning one type from the database.</p>\n<p>We call the <code>QueryAsync</code> method and specify <code>LineItem</code> as the return type.\nMake sure to pass in the arguments for this method, the SQL query, and the <code>OrderId</code> parameter.\nI prefer creating anonymous objects for Dapper parameters.</p>\n<pre><code class=\"language-csharp\">using var connection = new SqlConnection();\n\nvar lineItems = await connection.QueryAsync&lt;LineItem&gt;(\n    sql,\n    new { OrderId = orderId });\n</code></pre>\n<p>That's everything you need for a simple mapping.</p>\n<h2>Dapper One To One Relationship Mapping</h2>\n<p>What if the object we want to return from the SQL query contains a <strong>nested object</strong>?</p>\n<p>Here's an updated <code>LineItem</code> type that also contains a <code>Product</code> inside.</p>\n<pre><code class=\"language-csharp\">public class LineItem\n{\n    public long LineItemId { get; init; }\n\n    public long OrderId { get; init; }\n\n    public decimal Price { get; init; }\n\n    public string Currency { get; init; }\n\n    public decimal Quantity { get; init; }\n\n    public Product Product { get; init; }\n}\n\npublic class Product\n{\n    public long ProductId { get; init; }\n\n    public string Name { get; init; }\n}\n</code></pre>\n<p>Now you need to return two types in the same query.</p>\n<p>Here's the updated SQL query with a join on the <code>Products</code> table:</p>\n<pre><code class=\"language-sql\">SELECT li.Id AS LineItemId, li.OrderId, li.Price, li.Currency, li.Quantity,\n       p.Id AS ProductId, p.Name\nFROM LineItems li\nJOIN Products p ON p.Id = li.ProductId\nWHERE li.OrderId = @OrderId\n</code></pre>\n<p>This query is more complicated because we need to use Dapper's <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>multi-mapping</strong></a> feature.</p>\n<p>In the <code>QueryAsync</code> method, we specify both <code>LineItem</code> and <code>Product</code> as return types and <code>LineItem</code> as the final return type for the method.</p>\n<p>We must also tell Dapper how to map the <code>LineItem</code> and <code>Product</code> from the result set into a single <code>LineItem</code> object.</p>\n<p>And we need to specify the <code>splitOn</code> argument, which tells Dapper where one object ends and the next begins.</p>\n<pre><code class=\"language-csharp\">using var connection = new SqlConnection();\n\nvar lineItems = await connection.QueryAsync&lt;LineItem, Product, LineItem&gt;(\n    sql,\n    (lineItem, product) =&gt;\n    {\n        lineItem.Product = product;\n\n        return lineItem;\n    },\n    new { OrderId = orderId },\n    splitOn: &quot;ProductId&quot;);\n</code></pre>\n<p>We write more code to make this work, but it should be easy to wrap your head around it.</p>\n<h2>Dapper One To Many Relationship Mapping</h2>\n<p>Another frequent situation is mapping a <strong>one-to-many relationship</strong> from SQL into an object model.</p>\n<p>Because you are joining two tables, the result set will contain duplicate data on the &quot;one&quot; side of the relationship.</p>\n<p>For this example, let's use an <code>Order</code> with a list of <code>LineItem</code> objects.</p>\n<pre><code class=\"language-csharp\">public class Order\n{\n    public long OrderId { get; init; }\n\n    public List&lt;LineItem&gt; LineItems { get; init; } = new();\n}\n\npublic class LineItem\n{\n    public long LineItemId { get; init; }\n\n    public long OrderId { get; init; }\n\n    public decimal Price { get; init; }\n\n    public string Currency { get; init; }\n\n    public decimal Quantity { get; init; }\n}\n</code></pre>\n<p>Here's the SQL query returning the data we need from the database:</p>\n<pre><code class=\"language-sql\">SELECT o.Id AS OrderId,\n       li.Id AS LineItemId, li.OrderId, li.Price, li.Currency, li.Quantity\nFROM Orders o\nJOIN LineItems li ON li.OrderId = o.Id\nWHERE o.Id = @OrderId\n</code></pre>\n<p>We're going to get back duplicate <code>Order</code> data because of the <code>JOIN</code>.\nBut we only want to return one <code>Order</code> with all the line items.</p>\n<p>The Dapper mapping function only gives us the <code>Order</code> and <code>LineItem</code> for the current row in the result set.</p>\n<p>One way to solve this is to use a <code>Dictionary</code> to store the <code>Order</code> and reuse it inside the mapping.</p>\n<ul>\n<li>Store the <code>Order</code> in the dictionary if it's not there</li>\n<li>If it is there, add the <code>LineItem</code> to the existing <code>Order</code> instance</li>\n</ul>\n<pre><code class=\"language-csharp\">using var connection = new SqlConnection();\n\nvar ordersDictionary = new Dictionary&lt;long, Order&gt;();\n\nawait connection.QueryAsync&lt;Order, LineItem, Order&gt;(\n    sql,\n    (order, lineItem) =&gt;\n    {\n        if (ordersDictionary.TryGetValue(order.OrderId, out var existingOrder))\n        {\n            order = existingOrder;\n        }\n        else\n        {\n            ordersDictionary.Add(order.OrderId, order);\n        }\n\n        order.LineItems.Add(lineItem);\n\n        return order;\n    },\n    new { OrderId = orderId },\n    splitOn: &quot;LineItemId&quot;);\n\nvar mappedOrder = ordersDictionary[orderId];\n</code></pre>\n<p>A <strong>many-to-many relationship</strong> would use the same idea, except you'll need two dictionaries for each side of the relationship.</p>\n<h2>In Summary</h2>\n<p><strong>Dapper</strong> is a fantastic library for writing fast database queries using SQL.</p>\n<p>Because of how SQL works, mapping into an object model is sometimes complicated.</p>\n<p>There are four common scenarios:</p>\n<ul>\n<li>Simple mapping - a flat structure mapped directly from SQL to an object</li>\n<li>One-to-one mapping - provide a mapping function to connect two objects</li>\n<li>One-to-many mapping - manage a dictionary for the &quot;one&quot; side of the relationship</li>\n<li>Many-to-many mapping - same as above, except you need a dictionary for both sides of the relationship</li>\n</ul>\n<p>Now you have a cheat sheet for mapping relationships with Dapper.</p>\n<p>Hope this was helpful.</p>\n<p>I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/mastering-dapper-relationship-mappings",
            "title": "Mastering Dapper Relationship Mappings",
            "summary": "Dapper is a lightweight object-relational mapper in .NET, easy to use and fast at the same time.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_050.png",
            "date_modified": "2023-08-12T00:00:00.000Z",
            "date_published": "2023-08-12T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/modular-monolith-communication-patterns",
            "content_html": "<p>Modules in a modular monolith communicate in two ways.\nSynchronous method calls on a module's public API are fast and easy to implement, but they couple the modules together.\nAsynchronous messaging through a message broker gives you loose coupling and availability, at the cost of another piece of infrastructure to manage.</p>\n<p><strong>Modular monoliths</strong> are becoming more popular in the software engineering community.</p>\n<p>The allure of <strong>Microservices</strong> is becoming less compelling.\nWe also have seasoned veterans of our industry saying you should reconsider:</p>\n<blockquote>\n<p>You shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile.</p>\n</blockquote>\n<p><em>— <a href=\"https://martinfowler.com/bliki/MonolithFirst.html\">Martin Fowler</a></em></p>\n<p>Modular monoliths give you the <strong>logical architecture</strong> of Microservices without the operational complexity.\nYou can safely determine the boundaries between modules.\nAnd refactoring is straightforward and less risky.\nThey can also be easily migrated into Microservices if you decide to do so.</p>\n<p>I've built and maintained several large <strong>Modular monolith</strong> systems in the past years.</p>\n<p>In this week's newsletter, I want to focus on the <strong>communication patterns</strong> in the Modular monolith architecture.</p>\n<p>But first, let me explain what is a <strong>Modular monolith</strong>.</p>\n<h2>What Is a Modular Monolith?</h2>\n<p>Here's one definition of what a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-architecture-dotnet\"><strong>Modular monolith</strong></a> is:</p>\n<blockquote>\n<p>A Modular Monolith is a software design approach in which a monolith is designed with an emphasis on interchangeable (and potentially reusable) modules.</p>\n</blockquote>\n<p>The problem with most monolith systems is that they become <strong>tightly coupled</strong> over time.\nComponents are deeply intertwined.\nMaking a change in one component impacts many others.\nIntroducing new features is difficult and error-prone.</p>\n<p>Modular monoliths aim to solve these problems.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_049/modular_monolith_diagram.png\" alt=\"Comparison of tightly coupled monolith modules with isolated modules in a modular monolith\">\n<p>A Modular monolith consists of many <strong>loosely coupled</strong> modules.\nModules represent cohesive sets of functionalities.\nModules are also <strong>independent</strong> of each other.</p>\n<p>Here are a few examples:</p>\n<ul>\n<li>Payments module</li>\n<li>Shipping module</li>\n<li>Booking module</li>\n<li>Reviews module</li>\n</ul>\n<p>If this concept reminds you of Microservices, that's because it should.</p>\n<p>Microservices represent self-contained services encapsulating a set of functionalities of the larger system, much like modules in a Modular monolith.</p>\n<p>For a Modular monolith to be loosely coupled, you need to solve how modules will communicate.</p>\n<p>Modules cannot reference each other directly except through their <a href=\"https://milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths\"><strong>public APIs</strong></a>.</p>\n<p>There are two widely used communication patterns.\nBoth have pros and cons and a set of tradeoffs that you need to understand.</p>\n<h2>Synchronous Communication With Method Calls</h2>\n<p>The first and easiest communication pattern is simple <strong>method calls</strong> between modules.\nMethod calls are <strong>synchronous</strong> and very fast because they're in memory.</p>\n<p>Module A calls a method declared on the <strong>public API</strong> of Module B and waits until it receives a result.</p>\n<p>Each module exposes a <strong>public API</strong>, which can be an <code>interface</code> in .NET.</p>\n<p>The module will implement this interface internally and hide any implementation details.\nYou can use the <code>internal</code> keyword to make the implementation inaccessible outside the module.</p>\n<p>Modules depend on the interfaces at compile-time.\nAt runtime, <strong>dependency injection</strong> will provide the respective implementation.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_049/modular_monolith_sync_communication.png\" alt=\"Module A calling Module B synchronously through its public API\">\n<p>The benefits of this approach are:</p>\n<ul>\n<li>Speed of in-memory calls</li>\n<li>Easy to implement</li>\n<li>No indirection</li>\n</ul>\n<p>But, the drawback of this approach is <strong>strong coupling</strong>.</p>\n<p><strong>Synchronous communication</strong> means that the modules will be tightly coupled.\nIf one of the modules is unavailable, it will affect any dependent modules.\nYou can introduce a retry mechanism, but this only goes so far.</p>\n<h2>Asynchronous Communication With Messaging</h2>\n<p>The second communication pattern is asynchronous <a href=\"https://milanjovanovic.tech/blog/event-driven-communication-modules\"><strong>messaging</strong></a> between modules.</p>\n<p>Module A sends a message to the message broker in a fire-and-forget fashion.\nModule B subscribes to relevant messages and handles them accordingly.</p>\n<p>Modules don't need to know about each other, but they do need to know about the message contracts.</p>\n<p><strong>Message contracts</strong> are the <strong>public API</strong> of a module in this scenario.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_049/modular_monolith_async_communication.png\" alt=\"Modules A and B exchanging JSON messages asynchronously through a message broker\">\n<p>The benefits of this approach are:</p>\n<ul>\n<li>High availability</li>\n<li>Loose coupling</li>\n</ul>\n<p><strong>Asynchronous communication</strong> gives us loose coupling because modules communicate using messages.\nModule B doesn't need to be available for Module A to send a message.</p>\n<p>The obvious drawback of this approach is <strong>increased complexity</strong>.</p>\n<p>We're introducing a <strong>message broker</strong> to the system.\nThis is another infrastructure component we have to manage.\nIt's also a single point of failure.\nIf the message broker fails, so does communication between the modules.</p>\n<p>You can prevent message loss by storing messages in an <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox</strong></a> before publishing them.\nWe can always send the messages again from the database in case of a message broker failure.</p>\n<h2>Takeaway</h2>\n<p><strong>Synchronous communication</strong> between modules is easy to implement, and it's performant. But it comes at the cost of tight coupling between modules.</p>\n<p><strong>Asynchronous communication</strong> using a message broker is loosely coupled. But it's more complex to implement.</p>\n<p>So which <strong>communication pattern</strong> should you be using?</p>\n<p>It depends.</p>\n<p>Asynchronous communication can help you build <strong>loosely coupled</strong> and <strong>independent</strong> modules.\nMigrating a <strong>Modular monolith</strong> using messaging into a <strong>distributed system</strong> is much easier.</p>\n<p>You extract a module into its own deployment unit.\nAnd the communication between modules remains the same.\nBecause you are using messaging, you don't need to reimplement anything.</p>\n<p><strong>Synchronous method calls</strong> are an excellent choice to increase development velocity and reduce operational complexity.</p>\n<p>I'll let the software architect in you decide.</p>\n<p>Thanks for reading.</p>\n<p>And stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/modular-monolith-communication-patterns",
            "title": "Modular Monolith Communication Patterns",
            "summary": "Modular monoliths give you the logical architecture of microservices without the operational complexity.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_049.png",
            "date_modified": "2023-08-05T00:00:00.000Z",
            "date_published": "2023-08-05T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/why-clean-architecture-is-great-for-complex-projects",
            "content_html": "<p>Clean Architecture is a domain-centric way to organize a system into four layers: Domain, Application, Infrastructure, and Presentation.\nAll dependencies point inwards, so the core domain stays independent of the UI, databases, and external services.\nIt pays off when the business logic is complex, and it can be over-engineering on a small project.</p>\n<p>I've been using <strong>Clean Architecture</strong> for 6+ years on large-scale applications serving thousands of customers and millions of requests.\nToday I want to talk about why it's a great approach for structuring your applications.</p>\n<p>I'm aware that Clean Architecture isn't a silver bullet, so I will discuss what types of systems can benefit from this architecture.</p>\n<p>Clean architecture isn't revolutionary.</p>\n<p>But it's <strong>prescriptive</strong> about how you should structure the code.</p>\n<p>It's an evolution of layered architecture, focusing on the core domain and the direction of dependencies.\nAll dependencies should point inwards, applying <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>dependency inversion</strong></a>.</p>\n<p>Here are some of the promises of Clean Architecture:</p>\n<ul>\n<li>Maintainability</li>\n<li>Testability</li>\n<li>Loose coupling</li>\n<li>Separation of concerns</li>\n</ul>\n<p>It's <strong>independent</strong> of UI, databases, or external services - but you also need to be <strong>pragmatic</strong> (more on this later).</p>\n<p>Let's dive in!</p>\n<h2>What Is Clean Architecture?</h2>\n<p>Clean Architecture was created by <a href=\"https://en.wikipedia.org/wiki/Robert_C._Martin\"><strong>Robert C. Martin</strong></a>, aka Uncle Bob.</p>\n<p>Clean Architecture is an approach to organizing a software system to <strong>separate the concerns</strong> of the various components.\nMaking the system easier to understand and maintain.</p>\n<p>You can think about Clean Architecture as a domain-centric approach to organizing dependencies.</p>\n<p>There are similar architectures that follow the same domain-centric idea.\nYou may know them as the <a href=\"https://milanjovanovic.tech/blog/clean-architecture-vs-onion-vs-hexagonal\"><strong>Hexagonal and Onion architectures</strong></a>.\nThey are more or less interchangeable with each other.\nAnd they all place the core domain at the center of the architecture.</p>\n<p>This is my high-level interpretation of <strong>Clean Architecture</strong>:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_048/clean_architecture.png\" alt=\"Clean Architecture mapping entities, use cases, API endpoints, and external services to their respective layers\">\n<p>There are four layers inside:</p>\n<ul>\n<li>Domain</li>\n<li>Application</li>\n<li>Infrastructure</li>\n<li>Presentation</li>\n</ul>\n<p>Let's see what should live inside each layer.</p>\n<h2>Clean Architecture Layers</h2>\n<p>Here's a breakdown of what should live inside each <strong>layer</strong> of the <strong>Clean Architecture</strong>.</p>\n<p><strong>Domain</strong></p>\n<ul>\n<li>Contains the core business rules &amp; logic</li>\n<li>It should be independent of other layers in the system</li>\n<li>Should be persistence ignorant - the persistence mechanism shouldn't influence your domain model</li>\n<li><strong>Examples:</strong> Entities, Value Objects, Domain services, Domain events, Enums, Repository interfaces</li>\n</ul>\n<p><strong>Application</strong></p>\n<ul>\n<li>Contains the application use cases</li>\n<li>Contains application-specific business rules</li>\n<li>Orchestrates the domain entities to perform business operations</li>\n<li>It should be independent of external concerns (but it doesn't have to be)</li>\n<li><strong>Examples:</strong> Application services, Commands, Queries, External service interfaces, Exceptions</li>\n</ul>\n<p><strong>Infrastructure</strong></p>\n<ul>\n<li>Contains anything related to external concerns</li>\n<li>Implements interfaces defined in the layers below</li>\n<li><strong>Examples:</strong> PostgreSQL, Keycloak, AWS S3, RabbitMQ, Kafka, SendGrid</li>\n</ul>\n<p><strong>Presentation</strong></p>\n<ul>\n<li>Represents the entry point to the system</li>\n<li>Accepts data from the outside and passes it to the use cases</li>\n<li>Acts as the composition root for dependency injection</li>\n<li><strong>Examples:</strong> ASP.NET Core, gRPC</li>\n</ul>\n<p><a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure\"><strong>Here's an example</strong></a> of how to structure the Clean Architecture on a solution level.\nYou can also group related components together by feature.\nIt leads to better cohesion and is an excellent option if your project is more complex.</p>\n<p>I also made a few videos covering the Clean Architecture project setup:</p>\n<ul>\n<li><a href=\"https://youtu.be/tLk4pZZtiDY\"><strong>Clean Architecture walkthrough</strong></a> (100k+ views)</li>\n<li><a href=\"https://youtu.be/fe4iuaoxGbA\"><strong>Clean Architecture project setup from scratch</strong></a> (40k+ views)</li>\n</ul>\n<h2>Where Should You Use Clean Architecture?</h2>\n<p><strong>Clean Architecture</strong> is very versatile and applies to various domains and systems.</p>\n<p>But, you should play to its strengths and use it only when there's a tangible benefit.</p>\n<p>I use the <strong>Clean Architecture</strong> when I want to:</p>\n<ul>\n<li>Apply Domain-Driven Design</li>\n<li>Solve complex business logic</li>\n<li>Build highly testable projects</li>\n<li>Enforce design policies via the architecture</li>\n</ul>\n<p>If the above is true for your project, then Clean Architecture is an excellent option.</p>\n<p>You should also consider the <a href=\"https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design\"><strong>benefits of Clean Architecture.</strong></a></p>\n<h2>The Case For Being Pragmatic</h2>\n<p>I try to be <strong>pragmatic</strong> when using <strong>Clean Architecture</strong>.</p>\n<p>Applying what I like and having the freedom of &quot;breaking&quot; Clean Architecture if it will simplify things.</p>\n<p>Can this be called Clean Architecture, then?\nNo, not in the purest sense.</p>\n<p>But, I still get <strong>most</strong> of the <strong>benefits</strong> of Clean Architecture.</p>\n<p>Here's an example, when I'm applying <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\"><strong>CQRS</strong></a> in Clean Architecture to implement the use cases.</p>\n<p>On the command (write) side, it's valuable to be independent of external concerns, so I will use repositories behind an interface.\nI can control the repository contract, and unit testing is straightforward.</p>\n<p>But, on the query (read) side, I want to return the response as fast as possible.\nCreating an abstraction only adds indirection and reduces performance.</p>\n<p>A better approach is to use the EF or Dapper in the handler and query the database.\nIt's simple, fast, and you can use all the features offered by the ORM.\nYou don't need a lot of complexity or abstractions on the query side.</p>\n<p>I should call this approach <a href=\"https://milanjovanovic.tech/pragmatic-clean-architecture\"><strong>Pragmatic Clean Architecture</strong>.</a></p>\n<h2>Closing Thoughts</h2>\n<p><strong>Clean Architecture</strong> gives you a <strong>standard</strong> for organizing your solution.</p>\n<p>You don't have to reinvent the wheel every time at the start of the project.</p>\n<p>But, the layered structure and architectural constraints can increase the <strong>complexity</strong> of smaller projects.<br>\nSo make sure your project is complex enough to apply Clean Architecture.</p>\n<p>Another <strong>caveat</strong> of Clean Architecture is the danger of <strong>over-engineering</strong>.</p>\n<p>Don't follow the principles religiously without considering the specific project requirements.<br>\nThe overhead of maintaining so many layers and abstractions may not be justified.</p>\n<p>Be <strong>pragmatic</strong> and try to make the best decision possible.</p>\n<p>Sometimes that means straying from the paradigm.</p>\n<p>Hope this was helpful.</p>\n<p>I'll see you next week!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/why-clean-architecture-is-great-for-complex-projects",
            "title": "Why Clean Architecture Is Great For Complex Projects",
            "summary": "I've been using Clean Architecture for 6+ years on large scale applications serving thousands of customers.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_048.png",
            "date_modified": "2023-07-29T00:00:00.000Z",
            "date_published": "2023-07-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems",
            "content_html": "<p>A domain event is a Domain-Driven Design pattern that records a fact which already happened in the domain.\nOther components subscribe to that event and react to it, so the code raising the event stays decoupled from the side effects.\nIn .NET you can implement domain events as MediatR notifications and publish them from EF Core's <code>SaveChangesAsync</code>.</p>\n<p>In software engineering, &quot;coupling&quot; means how much different parts of a software system depend on each other.\nIf they are <strong>tightly coupled</strong>, changes to one part can affect many others.\nBut if they are <strong>loosely coupled</strong>, changes to one part won't cause big problems in the rest of the system.</p>\n<p><strong>Domain events</strong> are a <strong>Domain-Driven Design (DDD)</strong> tactical pattern that we can use to build <strong>loosely coupled</strong> systems.</p>\n<p>You can raise a <strong>domain event</strong> from the domain, which represents a fact that has occurred.\nAnd other components in the system can subscribe to this event and handle it accordingly.</p>\n<p>Here's what you will learn in this week's newsletter:</p>\n<ul>\n<li>What are <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#what-are-domain-events\">domain events</a></li>\n<li>How they're <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#domain-events-versus-integration-events\">different from integration events</a></li>\n<li>How to <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#implementing-domain-events\">implement</a> &amp; <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#raising-domain-events\">raise domain events</a></li>\n<li>How to <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#how-to-publish-domain-events-with-ef-core\">publish domain events</a> with EF Core</li>\n<li>How to <a href=\"https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems#how-to-handle-domain-events\">handle domain events</a> with MediatR</li>\n</ul>\n<p>We have a lot to cover, so let's dive in!</p>\n<h2>What Are Domain Events?</h2>\n<p>An <strong>event</strong> is something that has happened in the past.</p>\n<p>It is a fact.</p>\n<p>Unchangeable.</p>\n<p>A <strong>domain event</strong> is something that happened in the domain, and other parts of the domain should be aware of it.</p>\n<p><strong>Domain events</strong> allow you to express side effects explicitly, and provide a better separation of concerns in the domain.\nThey're an ideal way to trigger side effects across multiple aggregates inside the domain.</p>\n<p>It's your responsibility to ensure that publishing a <strong>domain event</strong> is transactional.\nYou'll see why this is easier said than done.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_047/domain_events.png\" alt=\"CourseCompleted domain event published to three application event handlers\">\n<h2>Domain Events Versus Integration Events</h2>\n<p>You may have heard of <a href=\"https://milanjovanovic.tech/blog/domain-events-vs-integration-events\"><strong>integration events</strong></a>, and you're now wondering what's the difference between them and <strong>domain events</strong>.</p>\n<p>Semantically, they're the same thing: a representation of something that occurred in the past.</p>\n<p>However, their <strong>intent is different</strong> and this is important to understand.</p>\n<p>Domain events:</p>\n<ul>\n<li>Published and consumed within a single domain</li>\n<li>Sent using an in-memory message bus</li>\n<li>Can be processed synchronously or asynchronously</li>\n</ul>\n<p>Integration events:</p>\n<ul>\n<li>Consumed by other subsystems (microservices, Bounded Contexts)</li>\n<li>Sent with a message broker over a queue</li>\n<li>Processed completely asynchronously</li>\n</ul>\n<p>So if you're wondering what type of event you should publish, think about the intent and who should be handling the event.</p>\n<p><strong>Domain events</strong> can also be used to <strong>generate integration events</strong>, which leave the domain boundary.</p>\n<h2>Implementing Domain Events</h2>\n<p>My preferred approach to implement <strong>domain events</strong> is creating an <code>IDomainEvent</code> abstraction and implementing MediatR <code>INotification</code>.</p>\n<p>The benefit is you can use <strong>MediatR's publish-subscribe</strong> support to publish a notification to one or multiple handlers.</p>\n<pre><code class=\"language-csharp\">using MediatR;\n\npublic interface IDomainEvent : INotification\n{\n}\n</code></pre>\n<p>Now you can implement a concrete domain event.</p>\n<p>Here are a few <strong>constraints</strong> to consider when <strong>designing domain events</strong>:</p>\n<ul>\n<li>Immutability - domain events are facts, and should be immutable</li>\n<li>Fat vs Thin domain events - how much information do you need?</li>\n<li>Use past tense for event naming</li>\n</ul>\n<pre><code class=\"language-csharp\">public class CourseCompletedDomainEvent : IDomainEvent\n{\n    public Guid CourseId { get; init; }\n}\n</code></pre>\n<h2>Raising Domain Events</h2>\n<p>After you create your domain events, you want to raise them from the domain.</p>\n<p>My approach is creating an <code>Entity</code> base class, because only entities are allowed to raise domain events.\nYou can further encapsulate raising domain events by making the <code>RaiseDomainEvent</code> method <code>protected</code>.</p>\n<p>We're storing domain events in an internal collection, to prevent anyone else from accessing it.\nThe <code>GetDomainEvents</code> method is there to get a snapshot of the collection, and the <code>ClearDomainEvents</code> method to clear the internal collection.</p>\n<pre><code class=\"language-csharp\">public abstract class Entity : IEntity\n{\n    private readonly List&lt;IDomainEvent&gt; _domainEvents = new();\n\n    public IReadOnlyList&lt;IDomainEvent&gt; GetDomainEvents()\n    {\n        return _domainEvents.ToList();\n    }\n\n    public void ClearDomainEvents()\n    {\n        _domainEvents.Clear();\n    }\n\n    protected void RaiseDomainEvent(IDomainEvent domainEvent)\n    {\n        _domainEvents.Add(domainEvent);\n    }\n}\n</code></pre>\n<p>Now you're entities can inherit from the <code>Entity</code> base class and raise domain events:</p>\n<pre><code class=\"language-csharp\">public class Course : Entity\n{\n    public Guid Id { get; private set; }\n\n    public CourseStatus Status { get; private set; }\n\n    public DateTime? CompletedOnUtc { get; private set; }\n\n    public void Complete()\n    {\n        Status = CourseStatus.Completed;\n        CompletedOnUtc = DateTime.UtcNow;\n\n        RaiseDomainEvent(new CourseCompletedDomainEvent { CourseId = this.Id });\n    }\n}\n</code></pre>\n<p>And all that's left to do is <strong>publish</strong> the <strong>domain events</strong>.</p>\n<h2>How To Publish Domain Events With EF Core</h2>\n<p>An elegant solution for publishing domain events is using <strong>EF Core</strong>.</p>\n<p>Since EF Core acts as a <strong>Unit of Work</strong>, you can use it to gather all <strong>domain events</strong> in the current transaction and publish them.</p>\n<p>I don't like to complicate things, and simply override the <code>SaveChangesAsync</code> method to publish the domain events after persisting the changes in the database.\nBut you could also use an <a href=\"https://milanjovanovic.tech/blog/how-to-use-ef-core-interceptors\"><strong>interceptor</strong></a>.</p>\n<pre><code class=\"language-csharp\">public class ApplicationDbContext : DbContext\n{\n    public override async Task&lt;int&gt; SaveChangesAsync(\n        CancellationToken cancellationToken = default)\n    {\n        // When should you publish domain events?\n        //\n        // 1. BEFORE calling SaveChangesAsync\n        //     - domain events are part of the same transaction\n        //     - immediate consistency\n        // 2. AFTER calling SaveChangesAsync\n        //     - domain events are a separate transaction\n        //     - eventual consistency\n        //     - handlers can fail\n\n        var result = await base.SaveChangesAsync(cancellationToken);\n\n        await PublishDomainEventsAsync();\n\n        return result;\n    }\n}\n</code></pre>\n<p>The most <strong>important decision</strong> you will have to make here is <strong>when to publish the domain events</strong>.</p>\n<p>I think it makes the most sense to publish after calling <code>SaveChangesAsync</code>.\nIn other words, after saving changes to the database.</p>\n<p>This comes with a few tradeoffs:</p>\n<ul>\n<li>Eventual consistency - because messages are processed after the original transactions</li>\n<li>Database inconsistency risk - because handling domain events can fail</li>\n</ul>\n<p>Eventual consistency is something I can live with, so I choose to make this tradeoff.</p>\n<p>However, introducing a risk of database inconsistency is a big concern.</p>\n<p>You can solve this with the <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox pattern,</strong></a>\nwhere you persist your changes to the database and the domain events (as outbox messages) in a single transaction.\nNow you have a guaranteed atomic transaction, and the domain events are processed asynchronously using a background job.</p>\n<p>If you're wondering what's inside the <code>PublishDomainEventsAsync</code> method:</p>\n<pre><code class=\"language-csharp\">private async Task PublishDomainEventsAsync()\n{\n    var domainEvents = ChangeTracker\n        .Entries&lt;Entity&gt;()\n        .Select(entry =&gt; entry.Entity)\n        .SelectMany(entity =&gt;\n        {\n            var domainEvents = entity.GetDomainEvents();\n\n            entity.ClearDomainEvents();\n\n            return domainEvents;\n        })\n        .ToList();\n\n    foreach (var domainEvent in domainEvents)\n    {\n        await _publisher.Publish(domainEvent);\n    }\n}\n</code></pre>\n<h2>How To Handle Domain Events</h2>\n<p>With all of the plumbing we created so far, we're ready to implement a handler for the domain events.\nLuckily, this is the simplest step in the process.</p>\n<p>All you have to do is define a class implementing <code>INotificationHandler&lt;T&gt;</code> and specify your domain event type as the generic argument.</p>\n<p>Here's a handler for the <code>CourseCompletedDomainEvent</code>, which takes the domain event and publishes a <code>CourseCompletedIntegrationEvent</code> to notify other systems.</p>\n<pre><code class=\"language-csharp\">public class CourseCompletedDomainEventHandler\n    : INotificationHandler&lt;CourseCompletedDomainEvent&gt;\n{\n    private readonly IBus _bus;\n\n    public CourseCompletedDomainEventHandler(IBus bus)\n    {\n        _bus = bus;\n    }\n\n    public async Task Handle(\n        CourseCompletedDomainEvent domainEvent,\n        CancellationToken cancellationToken)\n    {\n        await _bus.Publish(\n            new CourseCompletedIntegrationEvent(domainEvent.CourseId),\n            cancellationToken);\n    }\n}\n</code></pre>\n<h2>In Summary</h2>\n<p><strong>Domain events</strong> can help you build a loosely coupled system.\nYou can use them to separate the core domain logic from the side effects, which can be handled asynchronously.</p>\n<p>There's no need to reinvent the wheel for implementing domain events, and you can use the <strong>EF Core</strong> and <strong>MediatR</strong> libraries to build this.</p>\n<p>You will have to make the decision when you want to publish domain events.\nPublishing before or after saving changes to the database both have their set of <strong>tradeoffs</strong>.</p>\n<p>I prefer <strong>publishing</strong> domain events <strong>after saving changes</strong> to the database, and I use the <a href=\"https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging\"><strong>Outbox pattern</strong></a>\nto add transactional guarantees.\nThis approach introduces eventual consistency, but it's also more reliable.</p>\n<p>Hope this was helpful.</p>\n<p>See you next week!</p>\n<p><strong>Today's action step:</strong>\nTake a look at <a href=\"https://youtu.be/AHzWJ_SMqLo\"><strong>this video,</strong></a> where I explain how to implement domain events to build a decoupled system that scales.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-domain-events-to-build-loosely-coupled-systems",
            "title": "How To Use Domain Events To Build Loosely Coupled Systems",
            "summary": "Domain events are a Domain-Driven Design tactical pattern for building loosely coupled systems.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_047.png",
            "date_modified": "2023-07-22T00:00:00.000Z",
            "date_published": "2023-07-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/8-tips-to-write-clean-code",
            "content_html": "<p>Clean code is easy to read and maintain, and refactoring exercises are how you practice writing it.\nThis issue takes one badly written <code>Process</code> method through eight steps: early returns, merged conditions, LINQ, a descriptive method name, custom exceptions, constants for magic numbers, enums for magic strings, and the result object pattern.</p>\n<p><strong>Clean code</strong> is code that's easy to read, maintain, and understand.</p>\n<p>I consider writing <strong>clean code</strong> a skill.</p>\n<p>And it's a <strong>skill</strong> that <strong>you can learn</strong> and improve with deliberate practice.</p>\n<p>My favorite approach for practicing <strong>clean coding</strong> is doing <a href=\"https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips\"><strong>refactoring exercises</strong></a>.</p>\n<p>So I prepared one for you today, and we're going to improve it one step at a time by applying <strong>clean code principles</strong>.</p>\n<p>Let's dive in!</p>\n<h2>Starting Point</h2>\n<p>I like starting with a problem when trying to learn new concepts.</p>\n<p>And the more illustrative the problem, the better.</p>\n<p>So we'll use some poorly written code as the starting point for our refactoring.</p>\n<p>And in each step, I will highlight what the current issue is and how we will fix it.</p>\n<p>Here's what I see when I look at the <code>Process</code> method:</p>\n<ul>\n<li>Deep nesting of code - 4 levels, to be precise</li>\n<li>Precondition checks are applied one after the other</li>\n<li>Throwing exceptions to represent a failure</li>\n</ul>\n<p>How can we turn this into <strong>clean code</strong>?</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (order != null)\n    {\n        if (order.IsVerified)\n        {\n            if (order.Items.Count &gt; 0)\n            {\n                if (order.Items.Count &gt; 15)\n                {\n                    throw new Exception(\n                        &quot;The order &quot; + order.Id + &quot; has too many items&quot;);\n                }\n\n                if (order.Status != &quot;ReadyToProcess&quot;)\n                {\n                    throw new Exception(\n                        &quot;The order &quot; + order.Id + &quot; isn't ready to process&quot;);\n                }\n\n                order.IsProcessed = true;\n            }\n        }\n    }\n}\n</code></pre>\n<h2>#1: Early Return Principle</h2>\n<p>It should be painfully obvious by now that the initial version is deeply nested because of the <code>if</code> statements applying precondition checks.</p>\n<p>We'll solve this using the <strong>early return principle</strong>, which states that we should return from a method as soon as the conditions for that have been met.</p>\n<p>In the case of the <code>Process</code> method, this means moving from a deeply nested structure to a set of <strong>guard clauses</strong>.</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (order is null)\n    {\n        return;\n    }\n\n    if (!order.IsVerified)\n    {\n        return;\n    }\n\n    if (order.Items.Count == 0)\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; 15)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; has too many items&quot;);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; isn't ready to process&quot;);\n    }\n\n    order.IsProcessed = true;\n}\n</code></pre>\n<h2>#2: Merge If Statements To Improve Readability</h2>\n<p>The <strong>early return principle</strong> makes the <code>Process</code> method more readable.</p>\n<p>But there's no need to have one <strong>guard clause</strong> after another.</p>\n<p>So we can merge all of them into one <code>if</code> statement.</p>\n<p>The behavior of the <code>Process</code> method remains unchanged, but we remove a lot of excess code.</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (order is null ||\n        !order.IsVerified ||\n        order.Items.Count == 0)\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; 15)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; has too many items&quot;);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; isn't ready to process&quot;);\n    }\n\n    order.IsProcessed = true;\n}\n</code></pre>\n<h2>#3: Use LINQ For More Concise Code</h2>\n<p>A quick improvement can be using <strong>LINQ</strong> to make the code more concise and expressive.</p>\n<p>Instead of checking for <code>Items.Count == 0</code>, I prefer using the LINQ <code>Any</code> method.</p>\n<p>You could argue that LINQ has worse performance, but I always optimize for readability.</p>\n<p>There are far more expensive operations in an application than a simple method call.</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (order is null ||\n        !order.IsVerified ||\n        !order.Items.Any())\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; 15)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; has too many items&quot;);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; isn't ready to process&quot;);\n    }\n\n    order.IsProcessed = true;\n}\n</code></pre>\n<h2>#4: Replace Boolean Expression With Descriptive Method</h2>\n<p>Merging multiple conditions into one <code>if</code> statement means writing less code, but it can <strong>decrease readability</strong> with <strong>complex conditions</strong>.</p>\n<p>However, you can fix this and improve readability by using a variable or method with a <strong>descriptive name</strong>.</p>\n<p>I prefer using methods, so I will introduce the <code>IsProcessable</code> method to represent the precondition check.</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (!IsProcessable(order))\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; 15)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; has too many items&quot;);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new Exception(\n            &quot;The order &quot; + order.Id + &quot; isn't ready to process&quot;);\n    }\n\n    order.IsProcessed = true;\n}\n\nstatic bool IsProcessable(Order? order)\n{\n    return order is not null &amp;&amp;\n           order.IsVerified &amp;&amp;\n           order.Items.Any();\n}\n</code></pre>\n<h2>#5: Prefer Throwing Custom Exceptions</h2>\n<p>Now let's talk about throwing exceptions.\nI like to use exceptions for <em>&quot;exceptional&quot;</em> situations only, and I don't use them for flow control in my code.</p>\n<p>Having said that, if you <em>do</em> want to use exceptions for flow control, it's better to use <strong>custom exceptions</strong>.</p>\n<p>You can introduce valuable contextual information and better describe the reason for throwing the exception.</p>\n<p>And if you want to <a href=\"https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8\"><strong>handle these exceptions globally</strong></a>, you can create a base class to be able to catch specific exceptions.</p>\n<pre><code class=\"language-csharp\">public void Process(Order? order)\n{\n    if (!IsProcessable(order))\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; 15)\n    {\n        throw new TooManyLineItemsException(order.Id);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new NotReadyForProcessingException(order.Id);\n    }\n\n    order.IsProcessed = true;\n}\n\nstatic bool IsProcessable(Order? order)\n{\n    return order is not null &amp;&amp;\n           order.IsVerified &amp;&amp;\n           order.Items.Any();\n}\n</code></pre>\n<h2>#6: Fix Magic Numbers With Constants</h2>\n<p>A common <strong>code smell</strong> I see is the use of <strong>magic numbers</strong>.</p>\n<p>They are usually easy to spot because they're used to check if numeric some condition applies.</p>\n<p>The problem with <strong>magic numbers</strong> is that they <strong>carry no meaning</strong>.</p>\n<p>The code is harder to reason about, and more error-prone.</p>\n<p>Fixing <strong>magic numbers</strong> should be straightforward, and one solution is introducing a constant.</p>\n<pre><code class=\"language-csharp\">const int MaxNumberOfLineItems = 15;\n\npublic void Process(Order? order)\n{\n    if (!IsProcessable(order))\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; MaxNumberOfLineItems)\n    {\n        throw new TooManyLineItemsException(order.Id);\n    }\n\n    if (order.Status != &quot;ReadyToProcess&quot;)\n    {\n        throw new NotReadyForProcessingException(order.Id);\n    }\n\n    order.IsProcessed = true;\n}\n\nstatic bool IsProcessable(Order? order)\n{\n    return order is not null &amp;&amp;\n           order.IsVerified &amp;&amp;\n           order.Items.Any();\n}\n</code></pre>\n<h2>#7: Fix Magic Strings With Enums</h2>\n<p>Similar to <strong>magic numbers</strong>, we have the <strong>magic strings</strong> <strong>code smell</strong>.</p>\n<p>A typical use case for <strong>magic strings</strong> is to represent some sort of state.</p>\n<p>You'll notice that we're comparing the <code>Order.Status</code> value to a <strong>magic string</strong> to check if the order is ready to process.</p>\n<p>A few <strong>problems</strong> with <strong>magic strings</strong>:</p>\n<ul>\n<li>Easy to make mistakes (typo)</li>\n<li>Lack of strong typing</li>\n<li>Not refactoring proof</li>\n</ul>\n<p>Let's create an <code>OrderStatus</code> <code>enum</code> to represent the possible states:</p>\n<pre><code class=\"language-csharp\">enum OrderStatus\n{\n    Pending = 0,\n    ReadyToProcess = 1,\n    Processed = 2\n}\n</code></pre>\n<p>And now we have to use the appropriate <code>OrderStatus</code> in the check:</p>\n<pre><code class=\"language-csharp\">const int MaxNumberOfLineItems = 15;\n\npublic void Process(Order? order)\n{\n    if (!IsProcessable(order))\n    {\n        return;\n    }\n\n    if (order.Items.Count &gt; MaxNumberOfLineItems)\n    {\n        throw new TooManyLineItemsException(order.Id);\n    }\n\n    if (order.Status != OrderStatus.ReadyToProcess)\n    {\n        throw new NotReadyForProcessingException(order.Id);\n    }\n\n    order.IsProcessed = true;\n    order.Status = OrderStatus.Processed;\n}\n\nstatic bool IsProcessable(Order? order)\n{\n    return order is not null &amp;&amp;\n           order.IsVerified &amp;&amp;\n           order.Items.Any();\n}\n</code></pre>\n<h2>#8: Use The Result Object Pattern</h2>\n<p>I said I don't prefer using exceptions for flow control. But how can we fix this?</p>\n<p>One solution is using the <a href=\"https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern\"><strong>result object pattern</strong></a>.</p>\n<p>You can use a generic <code>Result</code> class to represent all types of results or a specific one like <code>ProcessOrderResult</code>.</p>\n<p>To make your result objects encapsulated, expose a set of factory methods to create the concrete result type.</p>\n<pre><code class=\"language-csharp\">public class ProcessOrderResult\n{\n    private ProcessOrderResult(\n        ProcessOrderResultType type,\n        long orderId,\n        string message)\n    {\n        Type = type;\n        OrderId = orderId;\n        Message = message;\n    }\n\n    public ProcessOrderResultType Type { get; }\n\n    public long OrderId { get; }\n\n    public string? Message { get; }\n\n    public static ProcessOrderResult NotProcessable() =&gt;\n      new(ProcessOrderResultType.NotProcessable, default, &quot;Not processable&quot;);\n\n    public static ProcessOrderResult TooManyLineItems(long oderId) =&gt;\n      new(ProcessOrderResultType.TooManyLineItems, orderId, &quot;Too many items&quot;);\n\n    public static ProcessOrderResult NotReadyForProcessing(long oderId) =&gt;\n      new(ProcessOrderResultType.NotReadyForProcessing, oderId, &quot;Not ready&quot;);\n\n    public static ProcessOrderResult Success(long oderId) =&gt;\n      new(ProcessOrderResultType.Success, oderId, &quot;Success&quot;);\n}\n</code></pre>\n<p>Using an <code>enum</code> like <code>ProcessOrderResultType</code> will make consuming the result object easier with switch expressions.\nHere's the <code>enum</code> to represent the <code>ProcessOrderResult.Type</code>:</p>\n<pre><code class=\"language-csharp\">public enum ProcessOrderResultType\n{\n    NotProcessable = 0,\n    TooManyLineItems = 1,\n    NotReadyForProcessing = 2,\n    Success = 3\n}\n</code></pre>\n<p>And now the <code>Process</code> method becomes:</p>\n<pre><code class=\"language-csharp\">const int MaxNumberOfLineItems = 15;\n\npublic ProcessOrderResult Process(Order? order)\n{\n    if (!IsProcessable(order))\n    {\n        return ProcessOrderResult.NotProcessable();\n    }\n\n    if (order.Items.Count &gt; MaxNumberOfLineItems)\n    {\n        return ProcessOrderResult.TooManyLineItems(order);\n    }\n\n    if (order.Status != OrderStatus.ReadyToProcess)\n    {\n        return ProcessOrderResult.NotReadyForProcessing(order);\n    }\n\n    order.IsProcessed = true;\n    order.Status = OrderStatus.Processed;\n\n    return ProcessOrderResult.Success(order);\n}\n\nstatic bool IsProcessable(Order? order)\n{\n    return order is not null &amp;&amp;\n           order.IsVerified &amp;&amp;\n           order.Items.Any();\n}\n</code></pre>\n<p>Here's how using an <code>enum</code> for the <code>ProcessOrderResult.Type</code> allows you to write a switch expression:</p>\n<pre><code class=\"language-csharp\">var result = Process(order);\n\nresult.Type switch\n{\n    ProcessOrderResultType.TooManyLineItems =&gt;\n        Console.WriteLine($&quot;Too many line items: {result.OrderId}&quot;),\n\n    ProcessOrderResultType.NotReadyForProcessing =&gt;\n        Console.WriteLine($&quot;Not ready for processing {result.OrderId}&quot;),\n\n    ProcessOrderResultType.Success =&gt;\n        Console.WriteLine($&quot;Processed successfully {result.OrderId}&quot;),\n\n    _ =&gt; Console.WriteLine(&quot;Failed to process: {OrderId}&quot;, result.OrderId),\n};\n</code></pre>\n<h2>Takeaway</h2>\n<p>That's it, 8 tips to write <strong>clean code</strong>:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#1-early-return-principle\">Early return principle</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#2-merge-if-statements-to-improve-readability\">Merge multiple if statements</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#3-use-linq-for-more-concise-code\">Use LINQ for conciseness</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#4-replace-boolean-expression-with-descriptive-method\">Replace boolean expression with method</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#5-prefer-throwing-custom-exceptions\">Prefer throwing custom exceptions</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#6-fix-magic-numbers-with-constants\">Replace magic numbers with constants</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#7-fix-magic-strings-with-enums\">Replace magic string with enums</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/8-tips-to-write-clean-code#8-use-the-result-object-pattern\">Use the result object pattern</a></li>\n</ul>\n<p>Writing <strong>clean code</strong> is a matter of deliberate practice and experience.</p>\n<p>Most people will read about <strong>clean coding principles</strong>, but few will strive to apply them daily.</p>\n<p>This is where you can set yourself apart.</p>\n<p>I also made a video about these <strong>clean code</strong> tips, you can watch it <a href=\"https://youtu.be/McDvyFglkvU\">here.</a></p>\n<p>Hope this was helpful.</p>\n<p>See you next week!</p>\n<p><strong>Today's action step:</strong>\nTake a look at your project and see if you're making some of the mistakes I highlighted here.\nAnd then fix them using the clean code tips I shared with you.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/8-tips-to-write-clean-code",
            "title": "8 Tips To Write Clean Code",
            "summary": "Clean code is code that's easy to read, maintain, and understand. It's a skill you can learn with deliberate practice.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_046.png",
            "date_modified": "2023-07-15T00:00:00.000Z",
            "date_published": "2023-07-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp",
            "content_html": "<p>An API gateway is a single entry point that accepts client calls and forwards them to the right microservice.\nYARP is Microsoft's open source reverse proxy library, and you configure its routes and clusters from application settings.\nBecause the gateway is an ASP.NET Core application, you can also apply authentication and rate limiting there.</p>\n<p>Large <strong>Microservice-based</strong> systems can consist of tens or even hundreds of individual services.\nA client application needs to have all of this information to be able to make requests to the relevant <strong>microservice</strong> directly.</p>\n<p>However, this has numerous issues, such as security concerns, increased complexity, and coupling.</p>\n<p>We can solve this by introducing an <strong>API gateway</strong> that acts as a <strong>reverse proxy</strong> to accept API calls from the client application and forward them to the appropriate service.</p>\n<p>The <strong>API gateway</strong> also enforces security and ensures scalability and high availability.</p>\n<p>In this week's newsletter, I'll show you how to implement an <strong>API gateway</strong> for your <strong>microservices system</strong> using the <strong>YARP reverse proxy</strong>.</p>\n<p>Here's what we will cover:</p>\n<ul>\n<li>Difference between <strong>API gateway</strong> and <strong>reverse proxy</strong></li>\n<li>Installing and configuring <strong>YARP</strong></li>\n<li>Creating an <strong>API gateway</strong> with <strong>YARP</strong></li>\n<li><strong>Authentication</strong> and <strong>rate limiting</strong> on the <strong>API gateway</strong></li>\n</ul>\n<p>Let's dive in.</p>\n<h2>What's The Difference Between an API Gateway And a Reverse Proxy?</h2>\n<p>A <strong>reverse proxy</strong> and an <strong>API gateway</strong> are similar concepts, but they serve different purposes.</p>\n<p>A <strong>reverse proxy</strong> acts as an intermediary between clients and servers.\nThe clients can only call the backend servers through the <strong>reverse proxy</strong>, which forwards the request to the appropriate server.\nIt hides the implementation details of individual servers inside the internal network.</p>\n<p>A <strong>reverse proxy</strong> is commonly used for:</p>\n<ul>\n<li>Load balancing</li>\n<li>Caching</li>\n<li>Security</li>\n<li>SSL termination</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_045/reverse_proxy.png\" alt=\"Reverse proxy routing web and mobile client requests to multiple application servers\">\n<p>An <strong>API gateway</strong> is a specific type of <strong>reverse proxy</strong> designed for managing APIs.\nIt acts as a single entry point for API consumers to the various backend services.</p>\n<p>The key characteristics of an <strong>API gateway</strong> are:</p>\n<ul>\n<li>Request routing and composition</li>\n<li>Request/response transformations</li>\n<li>Authentication and authorization</li>\n<li>Rate limiting</li>\n<li>Monitoring</li>\n</ul>\n<p>Also, note that an <strong>API gateway</strong> can perform <a href=\"https://milanjovanovic.tech/blog/horizontally-scaling-aspnetcore-apis-with-yarp-load-balancing\"><strong>load balancing</strong></a> and other functionalities mentioned for reverse proxies.</p>\n<p>Now let's see how to use a <strong>reverse proxy</strong> to implement an <strong>API gateway</strong>.</p>\n<h2>Installing And Configuring YARP</h2>\n<p><strong>YARP</strong> (Yet Another Reverse Proxy) is a library developed by Microsoft to address the needs of various teams needing to build a <strong>reverse proxy</strong>.\nIt's open source and built with .NET, so it integrates nicely with the existing ecosystem.</p>\n<p>Let's install <code>Yarp.ReverseProxy</code> <strong>NuGet</strong> package to get started:</p>\n<pre><code class=\"language-powershell\">Install-Package Yarp.ReverseProxy\n</code></pre>\n<p>Next, we're going to call:</p>\n<ul>\n<li><code>AddReverseProxy</code> to add the required services for <strong>YARP</strong></li>\n<li><code>LoadFromConfig</code> to load the <strong>reverse proxy</strong> configuration from application settings</li>\n<li><code>MapReverseProxy</code> to introduce the <strong>reverse proxy</strong> middleware</li>\n</ul>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddReverseProxy()\n    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;));\n\nvar app = builder.Build();\n\napp.MapReverseProxy();\n\napp.Run();\n</code></pre>\n<p>We need to tell the <strong>YARP</strong> reverse proxy how to route the incoming requests to the individual microservices.</p>\n<p><strong>YARP</strong> uses the concept of <code>Routes</code> to represent request patterns for the proxy and <code>Clusters</code> to represent the services to forward those requests.</p>\n<pre><code class=\"language-json\">{\n    &quot;ReverseProxy&quot;: {\n        &quot;Routes&quot;: {\n            ...\n        },\n        &quot;Clusters&quot;: {\n            ...\n        }\n    }\n}\n</code></pre>\n<p>Here's an example <strong>YARP configuration</strong> with a <code>{**catch-all}</code> pattern that will route all incoming requests to the one destination server.</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;ROUTE_NAME&quot;: {\n        &quot;ClusterId&quot;: &quot;CLUSTER_NAME&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;{**catch-all}&quot;\n        }\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;CLUSTER_NAME&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;https://www.milanjovanovic.tech/&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<h2>Implementing an API Gateway With YARP</h2>\n<p>We can use <strong>YARP</strong> to build an <strong>API gateway</strong> by providing the configuration for the services we want to route traffic to.</p>\n<p>I created a sample <a href=\"https://github.com/m-jovanovic/yarp-api-gateway-sample\">API gateway implementation with YARP</a> on GitHub, so you can give it a try.\nThe system has two services, the <code>Users.Api</code> and <code>Products.Api</code>, which are .NET 7 applications.</p>\n<p>If a request comes in matching the <code>/users-service/{**catch-all}</code>, for example <code>/users-service/users</code>, it will be routed to the <code>users-cluster</code>.\nThe same logic applies for the <code>products-cluster</code>. We can apply more advanced transformations through the <code>Transforms</code> section.</p>\n<pre><code class=\"language-json\">{\n  &quot;ReverseProxy&quot;: {\n    &quot;Routes&quot;: {\n      &quot;users-route&quot;: {\n        &quot;ClusterId&quot;: &quot;users-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/users-service/{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [{ &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }]\n      },\n      &quot;products-route&quot;: {\n        &quot;ClusterId&quot;: &quot;products-cluster&quot;,\n        &quot;Match&quot;: {\n          &quot;Path&quot;: &quot;/products-service/{**catch-all}&quot;\n        },\n        &quot;Transforms&quot;: [{ &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }]\n      }\n    },\n    &quot;Clusters&quot;: {\n      &quot;users-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;https://localhost:5201/&quot;\n          }\n        }\n      },\n      &quot;products-cluster&quot;: {\n        &quot;Destinations&quot;: {\n          &quot;destination1&quot;: {\n            &quot;Address&quot;: &quot;https://localhost:5101/&quot;\n          }\n        }\n      }\n    }\n  }\n}\n</code></pre>\n<p>We now have a functioning <strong>API gateway</strong> built with <strong>YARP</strong>, routing requests to two individual services.</p>\n<p>But what else can we do with <strong>YARP</strong>?</p>\n<h2>Adding Authentication</h2>\n<p>The <strong>API gateway</strong> can enforce <a href=\"https://milanjovanovic.tech/blog/implementing-api-gateway-authentication-with-yarp\"><strong>authentication</strong></a> and <strong>authorization</strong> at the entry point to the system before letting authenticated requests proceed.</p>\n<p>And <strong>YARP</strong> supports integrating with the existing authentication &amp; authorization middleware.</p>\n<p>You first need to define an <strong>authorization policy</strong>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddAuthorization(options =&gt;\n{\n    options.AddPolicy(&quot;authenticated&quot;, policy =&gt;\n        policy.RequireAuthenticatedUser());\n});\n</code></pre>\n<p>And call <code>UseAuthentication</code> and <code>UseAuthorization</code> to add the respective middleware to the request pipeline.\nIt's important to add them before calling <code>MapReverseProxy</code>.</p>\n<pre><code class=\"language-csharp\">app.UseAuthentication();\n\napp.UseAuthorization();\n\napp.MapReverseProxy();\n</code></pre>\n<p>Now all you have to do is add the <code>AuthorizationPolicy</code> section to the reverse proxy configuration:</p>\n<pre><code class=\"language-json\">&quot;users-route&quot;: {\n  &quot;ClusterId&quot;: &quot;users-cluster&quot;,\n  &quot;AuthorizationPolicy&quot;: &quot;authenticated&quot;,\n  &quot;Match&quot;: {\n    &quot;Path&quot;: &quot;/users-service/{**catch-all}&quot;\n  },\n  &quot;Transforms&quot;: [\n    { &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }\n  ]\n}\n</code></pre>\n<p><strong>YARP</strong> will forward most credentials to the proxied services, such as cookies or bearer tokens because it might be important to identify the user in the individual microservices.</p>\n<h2>Adding Rate Limiting</h2>\n<p>You can also use an <strong>API gateway</strong> to introduce <strong>rate limiting</strong> to your system.\nIt's a technique to limit the number of requests to your API to <strong>improve security</strong> and reduce the load on the servers.</p>\n<p>You can learn more about <a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core\">how to use rate limiting in .NET here.</a></p>\n<p>As you can already guess, <strong>YARP</strong> supports the native <strong>rate limiting</strong> mechanism added in .NET 7.</p>\n<p>All you need to do is define a <strong>rate limit policy</strong>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    rateLimiterOptions.AddFixedWindowLimiter(&quot;fixed&quot;, options =&gt;\n    {\n        options.Window = TimeSpan.FromSeconds(10);\n        options.PermitLimit = 5;\n    });\n});\n</code></pre>\n<p>Then you need to call <code>UseRateLimiter</code> to add the rate limiter middleware to the request pipeline.\nIt's important to do it before calling <code>MapReverseProxy</code>.</p>\n<pre><code class=\"language-csharp\">app.UseRateLimiter();\n\napp.MapReverseProxy();\n</code></pre>\n<p>And then, you can apply rate limiting to the desired route using the <code>RateLimiterPolicy</code> section in the reverse proxy configuration:</p>\n<pre><code class=\"language-json\">&quot;products-route&quot;: {\n  &quot;ClusterId&quot;: &quot;products-cluster&quot;,\n  &quot;RateLimiterPolicy&quot;: &quot;fixed&quot;,\n  &quot;Match&quot;: {\n    &quot;Path&quot;: &quot;/products-service/{**catch-all}&quot;\n  },\n  &quot;Transforms&quot;: [\n    { &quot;PathPattern&quot;: &quot;{**catch-all}&quot; }\n  ]\n}\n</code></pre>\n<h2>In Summary</h2>\n<p>An <strong>API gateway</strong> is a critical component for a robust <strong>microservices system</strong> implementation.</p>\n<p>And <strong>YARP</strong> is an excellent option if you want to build an <strong>API gateway</strong> with .NET.</p>\n<p>I created a sample API gateway implementation with YARP, which you can find <a href=\"https://github.com/m-jovanovic/yarp-api-gateway-sample\">here.</a>\nThe system consists of two APIs, and the API gateway is configured to route requests between them.\nIt also implements:</p>\n<ul>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/articles/authn-authz.html\">Authentication</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core\">Rate limiting</a></li>\n</ul>\n<p>In this newsletter, we only scratched the surface of what's possible with <strong>YARP</strong>.</p>\n<p>Here are some useful resources if you want to learn more:</p>\n<ul>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/articles/index.html\">YARP docs</a></li>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/articles/load-balancing.html\">Load balancing</a></li>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/articles/session-affinity.html\">Session affinity</a></li>\n<li><a href=\"https://microsoft.github.io/reverse-proxy/articles/transforms.html\">Request/response transformations</a></li>\n</ul>\n<p>That's all for today.</p>\n<p>Hope it was helpful.</p>\n<p><strong>Today's action step:</strong>\nDownload the <a href=\"https://github.com/m-jovanovic/yarp-api-gateway-sample\">source code</a> for the sample application implementing an API gateway with YARP, and take it for a spin.\nYou can challenge yourself by creating multiple instances of a single service and configuring load balancing with the various load balancing algorithms.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp",
            "title": "Implementing an API Gateway For Microservices With YARP",
            "summary": "A client application shouldn't need to know about every service in your system. An API gateway acts as a reverse proxy, accepting API calls and forwarding them…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_045.png",
            "date_modified": "2023-07-08T00:00:00.000Z",
            "date_published": "2023-07-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/response-compression-in-aspnetcore",
            "content_html": "<p>Response compression in ASP.NET Core takes two method calls: <code>AddResponseCompression</code> to register the services and <code>UseResponseCompression</code> to add the middleware.\nBrotli and Gzip providers are registered by default, and compression is not turned on for HTTPS unless you set <code>EnableForHttps</code> to <code>true</code>.\nIn a test, Brotli reduced a 4.5kB JSON response by roughly 93.5%.</p>\n<p>Reducing the size of your API responses can noticeably improve the performance of your application.</p>\n<p>And since network bandwidth is a limited resource, you should at least consider the benefits of <strong>response compression</strong>.</p>\n<p>Here's what you'll learn in this week's newsletter:</p>\n<ul>\n<li>How to configure <strong>response compression</strong> in .NET</li>\n<li>Server-based vs. application-based compression</li>\n<li>Possible <strong>security risks</strong> and <strong>mitigation strategies</strong></li>\n<li>How to configure the available compression providers</li>\n<li>How much network bandwidth you could be saving</li>\n</ul>\n<p>Let's get started!</p>\n<h2>Configuring Response Compression</h2>\n<p>Using <strong>response compression</strong> in an .NET applications is remarkably easy.</p>\n<p>You only have to call these two methods:</p>\n<ul>\n<li><code>AddResponseCompression</code> - to configure the default services for response compression</li>\n<li><code>UseResponseCompression</code> - to add the response compression middleware to the <strong>request pipeline</strong></li>\n</ul>\n<p>The <code>UseResponseCompression</code> method should be called before any middleware that compresses responses.</p>\n<p><strong>Response compression</strong> isn't turned on by default for HTTPS, so you have to enable it by setting <code>EnableForHttps</code> to <code>true</code>.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddResponseCompression(options =&gt;\n{\n    options.EnableForHttps = true;\n});\n\nvar app = builder.Build();\n\napp.UseResponseCompression();\n\napp.MapGet(&quot;/&quot;, () =&gt; &quot;This response will be compressed 📦&quot;);\n\napp.Run();\n</code></pre>\n<p>It really is that simple.</p>\n<p>But...</p>\n<h2>When Should You Use Response Compression?</h2>\n<p>Ideally, you want to be using <strong>server-based response compression</strong> if your application server supports it.</p>\n<p>Because the middleware performs <strong>response compression</strong> at the <strong>application level</strong>, it will typically have worse performance.</p>\n<p>If you are hosting your application and you can't use server-based compression, then using the response compression middleware is justified.</p>\n<p>One more concern should be <strong>security</strong>, because using <strong>response compression over HTTPS</strong> can expose you to <a href=\"https://en.wikipedia.org/wiki/CRIME\">CRIME</a> and <a href=\"https://en.wikipedia.org/wiki/BREACH\">BREACH</a> attacks</p>\n<p>Here's what you can do to improve security:</p>\n<ul>\n<li>You can mitigate CRIME and BREACH attacks by introducing <strong>anti-forgery tokens</strong> in ASP.NET Core</li>\n<li>Don't send application secrets as part of the request body</li>\n<li>Implement a <a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core\"><strong>rate limiter</strong></a></li>\n</ul>\n<h2>Configuring Compression Providers</h2>\n<p>There are two compression providers added by default when you call <code>AddResponseCompression</code>:</p>\n<ul>\n<li><code>BrotliCompressionProvider</code></li>\n<li><code>GzipCompressionProvider</code></li>\n</ul>\n<p>You can further customize the available providers by adding custom compression providers if you want to.</p>\n<p>Compression will default to <strong>Brotli</strong> compression when it's supported by the client.\nOtherwise, it will default to <strong>Gzip</strong> if that is the supported compression format.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddResponseCompression(options =&gt;\n{\n    options.EnableForHttps = true;\n    options.Providers.Add&lt;BrotliCompressionProvider&gt;();\n    options.Providers.Add&lt;GzipCompressionProvider&gt;();\n    options.MimeTypes = ResponseCompressionDefaults.MimeTypes;\n});\n</code></pre>\n<p>The interesting thing is you can configure the <code>CompressionLevel</code> for the <strong>Brotli</strong> and <strong>Gzip</strong> compression providers.</p>\n<p>There are four possible values:</p>\n<ul>\n<li><code>Optimal</code> - tries to balance response size and compression speed</li>\n<li><code>Fastest</code> - sacrifices optimal compression for improved speed</li>\n<li><code>NoCompression</code> - self explanatory</li>\n<li><code>SmallestSize</code> - sacrifices compression speed to make the output as small as possible</li>\n</ul>\n<p>The default value is <code>CompressionLevel.Fastest</code>.</p>\n<pre><code class=\"language-csharp\">builder.Services.Configure&lt;BrotliCompressionProviderOptions&gt;(options =&gt;\n{\n    options.Level = CompressionLevel.Optimal;\n});\n\nbuilder.Services.Configure&lt;GzipCompressionProviderOptions&gt;(options =&gt;\n{\n    options.Level = CompressionLevel.SmallestSize;\n});\n</code></pre>\n<h2>How Much Can You Save?</h2>\n<p>Let's find out how much network bandwidth we can save by using <strong>response compression</strong>.</p>\n<p>Here's a minimal API endpoint returning a list of <code>Message</code> objects:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/&quot;, () =&gt; Results.Ok(\n    Enumerable\n        .Range(1, 100)\n        .Select(num =&gt; new Message\n        {\n            Id = num,\n            Content = $&quot;This is the message #{num}&quot;\n        })));\n</code></pre>\n<p>And here are the results with different providers and compression levels:</p>\n<ul>\n<li>No compression - 4.5kB</li>\n<li><strong>Gzip</strong> + <code>CompressionLevel.Fastest</code> - 569B</li>\n<li><strong>Gzip</strong> + <code>CompressionLevel.SmallestSize</code> - 539B</li>\n<li><strong>Gzip</strong> + <code>CompressionLevel.Optimal</code> - 554B</li>\n<li><strong>Brotli</strong> + <code>CompressionLevel.Fastest</code> - 400B</li>\n<li><strong>Brotli</strong> + <code>CompressionLevel.SmallestSize</code> - 296B</li>\n<li><strong>Brotli</strong> + <code>CompressionLevel.Optimal</code> - 319B</li>\n</ul>\n<p><strong>Brotli</strong> is the clear winner, which is why it's the <strong>default compression provider</strong>.</p>\n<p>In the best case scenario, you can reduce the response size by ~93.5%.\nMultiply this by the number of requests you're serving daily, and then you can begin to estimate the possible network savings.</p>\n<p>One more thing I noticed is that using <code>CompressionLevel.SmallestSize</code> has a noticeable <strong>negative impact</strong> on response time.\nI can't say this was surprising, so I suggest to simply keep using the default compression level.</p>\n<h2>In Summary</h2>\n<p><a href=\"https://learn.microsoft.com/en-us/aspnet/core/performance/response-compression?view=aspnetcore-7.0\">Response compression</a>\nis an interesting technique to <strong>improve API performance</strong> and reduce network costs.</p>\n<p>Ideally, you'd want to be using <strong>server-based response compression</strong> if it's supported by your application server.\nIf that's not the case, <strong>application-based compression</strong> is available in .NET with the response compression middleware.</p>\n<p>What's the cost of response compression?</p>\n<p>It will increase the CPU load, and can expose some security risks over HTTPS, but there are ways to mitigate this.</p>\n<p>In my experience, the <strong>default configuration values</strong> for the compression provider and compression level give excellent results.</p>\n<p>That's all for this week.</p>\n<p>See you next Saturday.</p>\n<p><strong>Today's action step:</strong>\nTo see the value of response compression, I suggest enabling it in your application and examining the changes in response size.\nYou can try the different compression providers by varying the <code>Accept-Encoding</code> header, and also configure different compression levels in your application.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/response-compression-in-aspnetcore",
            "title": "Response Compression In ASP.NET Core",
            "summary": "Reducing the size of your API responses can noticeably improve performance. Response compression in ASP.NET Core takes two method calls, and in the best case…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_044.png",
            "date_modified": "2023-07-01T00:00:00.000Z",
            "date_published": "2023-07-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr",
            "content_html": "<p>SignalR is the library you will most likely reach for when you need real-time functionality in .NET.\nIt lets you push content from server-side code to any connected clients as changes happen, and it abstracts away the message transport mechanism.\nThe one concept you need to grasp is the <code>Hub</code> class, which manages clients and sends messages.</p>\n<p>Today's modern applications must deliver the latest information without refreshing the user interface.</p>\n<p>If you need to introduce <strong>real-time</strong> functionality to your application in .NET, there's one library you will most likely reach for - <strong>SignalR</strong>.</p>\n<p><strong>SignalR</strong> allows you to push content from your server-side code to any connected clients as changes happen in real-time.</p>\n<p>Here's what I'll teach you in this week's newsletter:</p>\n<ul>\n<li>Creating your first <strong>SignalR</strong> <code>Hub</code></li>\n<li>Testing <strong>SignalR</strong> from <strong>Postman</strong></li>\n<li>Creating strongly typed hubs</li>\n<li>Sending messages to a specific user</li>\n</ul>\n<p>Let's see why <strong>SignalR</strong> is so powerful and how easy it is to build <strong>real-time</strong> applications with it.</p>\n<h2>Installing And Configuring SignalR</h2>\n<p>To start using <strong>SignalR</strong> you'll need to:</p>\n<ul>\n<li>Install the NuGet package</li>\n<li>Create the <code>Hub</code> class</li>\n<li>Register the SignalR services</li>\n<li>Map and expose the hub endpoint so clients can connect to it</li>\n</ul>\n<p>Let's start by installing the <code>Microsoft.AspNetCore.SignalR.Client</code> NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package Microsoft.AspNetCore.SignalR.Client\n</code></pre>\n<p>Then you need a SignalR <code>Hub</code>, which is the central component in your application responsible for managing clients and sending messages.</p>\n<p>Let's create a <code>NotificationsHub</code> by inheriting from the base <code>Hub</code> class:</p>\n<pre><code class=\"language-csharp\">public sealed class NotificationsHub : Hub\n{\n    public async Task SendNotification(string content)\n    {\n        await Clients.All.SendAsync(&quot;ReceiveNotification&quot;, content);\n    }\n}\n</code></pre>\n<p>The SignalR <code>Hub</code> exposes a few useful properties:</p>\n<ul>\n<li><code>Clients</code> - used to invoke methods on the clients connected to this hub</li>\n<li><code>Groups</code> - an abstraction for adding and removing connections from groups</li>\n<li><code>Context</code> - used for accessing information about the hub caller connection</li>\n</ul>\n<p>You can learn more about the <code>Hub</code> class <a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.hub?view=aspnetcore-7.0\">here</a>.</p>\n<p>Lastly, you need to register the SignalR services by calling the <code>AddSignalR</code> method.\nYou also need to call the <code>MapHub&lt;T&gt;</code> method, where you specify the <code>NotificationsHub</code> class and the path clients will use to connect to the hub.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddSignalR();\n\nvar app = builder.Build();\n\napp.MapHub&lt;NotificationsHub&gt;(&quot;notifications-hub&quot;);\n\napp.Run();\n</code></pre>\n<p>Now let's see how we can test the <code>NotificationsHub</code>.</p>\n<h2>Connecting To SignalR Hub From Postman</h2>\n<p>To test SignalR, you need a client that will connect to the <code>Hub</code> instance.\nYou could create a simple application with Blazor or JavaScript, but I will show you a different approach.</p>\n<p>We will use Postman's <strong>WebSocket Request</strong> to connect to the <code>NotificationsHub</code>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_043/postman_websocket_request.png\" alt=\"Postman new request menu with WebSocket selected\">\n<p>Here's what we need to do:</p>\n<ul>\n<li>Connect to the <code>NotificationsHub</code></li>\n<li>Set the communication protocol to JSON</li>\n<li>Send messages to call the <code>NotificationsHub</code> methods</li>\n</ul>\n<p>All messages need to end with a null termination character, which is just the ASCII character <code>0x1E</code>.</p>\n<p>Let's start off by sending this message to set the communication protocol to JSON:</p>\n<pre><code class=\"language-json\">{\n  &quot;protocol&quot;: &quot;json&quot;,\n  &quot;version&quot;: 1\n}?\n</code></pre>\n<p>You'll receive this response from the hub.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_043/postman_set_protocol_request.png\" alt=\"Postman WebSocket request sending the SignalR JSON protocol handshake to Notifications Hub\">\n<p>We need a slightly different message format to call a message on the <code>Hub</code>.\nThe key is specifying the <code>arguments</code> and <code>target</code>, which is the actual hub method we want to call.</p>\n<p>Let's say we want to call the <code>SendNotification</code> method on the <code>NotificationsHub</code>:</p>\n<pre><code class=\"language-json\">{\n  &quot;arguments&quot;: [&quot;This is the notification message.&quot;],\n  &quot;target&quot;: &quot;SendNotification&quot;,\n  &quot;type&quot;: 1\n}?\n</code></pre>\n<p>This will be the response we get back from the <code>NotificationsHub</code>:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_043/postman_send_notification_request.png\" alt=\"Postman WebSocket request invoking SendNotification and receiving a notification response\">\n<h2>Strongly Typed Hubs</h2>\n<p>The base <code>Hub</code> class uses the <code>SendAsync</code> method to send messages to connected clients.\nUnfortunately, we have to use strings to specify client-side methods to invoke, and it's easy to make a mistake.\nThere's also nothing enforcing which parameters are used.</p>\n<p>SignalR supports <strong>strongly typed hubs</strong> that aim to solve this.</p>\n<p>First, you need to define a client interface, so let's create a simple <code>INotificationsClient</code> abstraction:</p>\n<pre><code class=\"language-csharp\">public interface INotificationsClient\n{\n    Task ReceiveNotification(string content);\n}\n</code></pre>\n<p>The arguments don't have to be primitive types and can also be objects. SignalR will take care of serialization on the client side.</p>\n<p>After that, you need to update the <code>NotificationsHub</code> class to inherit from the <code>Hub&lt;T&gt;</code> class to make it strongly typed:</p>\n<pre><code class=\"language-csharp\">public sealed class NotificationsHub : Hub&lt;INotificationsClient&gt;\n{\n    public async Task SendNotification(string content)\n    {\n        await Clients.All.ReceiveNotification(content);\n    }\n}\n</code></pre>\n<p>You will lose access to the <code>SendAsync</code> method, and only the methods defined in your client interface will be available.</p>\n<h2>Sending Server-Side Messages With <code>HubContext</code></h2>\n<p>What good is a <code>NotificationsHub</code> if we can't send notifications from the backend to connected clients?<br>\nNot much.</p>\n<p>You can use the <code>IHubContext&lt;THub&gt;</code> interface access to the <code>Hub</code> instance in your backend code.</p>\n<p>And you can use <code>IHubContext&lt;THub, TClient&gt;</code> for a strongly typed hub.</p>\n<p>Here's a simple Minimal API endpoint that injects an <code>IHubContext&lt;NotificationsHub, INotificationsClient&gt;</code> for our strongly typed hub and uses it\nto send a notification to all connected clients:</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;notifications/all&quot;, async (\n    string content,\n    IHubContext&lt;NotificationsHub, INotificationsClient&gt; context) =&gt;\n{\n    await context.Clients.All.ReceiveNotification(content);\n\n    return Results.NoContent();\n});\n</code></pre>\n<h2>Sending Messages To a Specific User</h2>\n<p>The real value of SignalR is being able to <strong>send messages</strong>, or notifications in this example, to a <strong>specific user</strong>.</p>\n<p>I've seen some complicated implementations that manage a dictionary with a user identifier and a map of active connections.\nWhy would you do that when SignalR already supports this functionality?</p>\n<p>You can call the <code>User</code> method and pass it the <code>userId</code> to scope the <code>ReceiveNotification</code> message to that specific user.</p>\n<pre><code class=\"language-csharp\">app.MapPost(&quot;notifications/user&quot;, async (\n    string userId,\n    string content,\n    IHubContext&lt;NotificationsHub, INotificationsClient&gt; context) =&gt;\n{\n    await context.Clients.User(userId).ReceiveNotification(content);\n\n    return Results.NoContent();\n});\n</code></pre>\n<p>How does <strong>SignalR</strong> know which user to send the message to?</p>\n<p>It uses the <code>DefaultUserIdProvider</code> internally to extract the user identifier from the claims.\nTo be specific, it's using the <code>ClaimTypes.NameIdentifier</code> claim.\nThis also implies that you should be authenticated when connecting to the <code>Hub</code>, for example, by <strong>passing a JWT</strong>.</p>\n<pre><code class=\"language-csharp\">public class DefaultUserIdProvider : IUserIdProvider\n{\n    public virtual string? GetUserId(HubConnectionContext connection)\n    {\n        return connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;\n    }\n}\n</code></pre>\n<p>By default, all the methods in a hub can be called by unauthenticated users.\nSo you need to decorate it with an <code>Authorize</code> attribute to only allow authenticated clients to access the hub.</p>\n<pre><code class=\"language-csharp\">[Authorize]\npublic sealed class NotificationsHub : Hub&lt;INotificationsClient&gt;\n{\n    public async Task SendNotification(string content)\n    {\n        await Clients.All.ReceiveNotification(content);\n    }\n}\n</code></pre>\n<h2>In Summary</h2>\n<p>Adding <strong>real-time functionality</strong> to your application creates room for innovation and adds value to your users.</p>\n<p>With <strong>SignalR</strong>, you can start building real-time apps in .NET in minutes.</p>\n<p>You need to grasp one concept - the <code>Hub</code> class.\nSignalR abstracts away the message transport mechanism, so you don't have to worry about it.</p>\n<p>Make sure to send authenticated requests to <strong>SignalR</strong> hubs and turn on authentication on the <code>Hub</code>.\nSignalR will internally track the users connecting to your hubs, allowing you to send them messages based on the user identifier.</p>\n<p>That's all for today.</p>\n<p>Thanks for reading, and have an awesome Saturday.</p>\n<p><strong>Today's action step:</strong>\nLook at your project and try to find an opportunity to add real-time functionality.\nCommit 30 min. to build a simple proof of concept with SignalR and see if it can improve your project.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/adding-real-time-functionality-to-dotnet-applications-with-signalr",
            "title": "Adding Real-Time Functionality To .NET Applications With SignalR",
            "summary": "If you need real-time functionality in .NET, there's one library you will most likely reach for: SignalR.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_043.png",
            "date_modified": "2023-06-24T00:00:00.000Z",
            "date_published": "2023-06-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model",
            "content_html": "<p>An anemic domain model has only data properties and no behavior, so business logic ends up scattered across the application.\nA rich domain model fixes that by encapsulating the business logic inside the domain entities themselves.\nThe refactoring is gradual: hide constructors, encapsulate collections, move validation into entity methods, and raise domain events for side effects.</p>\n<p>Is the <strong>anemic domain model</strong> an <strong>antipattern</strong>?</p>\n<p>It's a domain model without any behavior and only data properties.</p>\n<p>Anemic domain models work great in simple applications, but they are difficult to maintain and evolve if you have rich business logic.</p>\n<p>The important parts of your business logic and rules end up being scattered all over the application.\nIt reduces cohesiveness and reusability, and makes adding new features more difficult.</p>\n<p><strong>Rich domain model</strong> attempts to solve this by encapsulating as much of the business logic as possible.</p>\n<p>But how can you design a <strong>rich domain model</strong>?</p>\n<p>This is a never-ending process of moving business logic into the domain and refining your domain model.</p>\n<p>Let's see how to <strong>refactor</strong> from an <strong>anemic domain model</strong> to a <strong>rich domain model</strong>.</p>\n<h2>Working With Anemic Domain Model</h2>\n<p>To understand what working with an <strong>anemic domain model</strong> looks like, I'll use an example of handling a <code>SendInvitationCommand</code>.</p>\n<p>I omitted the class and its dependencies so that we can focus on the <code>Handle</code> method.\nIt loads some entities from the database, performs validation, executes the business logic, and finally persists the changes in the database and sends an email.</p>\n<p>It already implements some good practices like using repositories and returning result objects.</p>\n<p>However, it's working with an <strong>anemic domain model</strong>.</p>\n<p>A few things indicating this:</p>\n<ul>\n<li>Parameterless constructors</li>\n<li>Public property setters</li>\n<li>Exposed collections</li>\n</ul>\n<p>In other words - the classes representing domain entities contain only data properties and no behavior.</p>\n<p>The <strong>problems</strong> of an <strong>anemic domain model</strong> are:</p>\n<ul>\n<li>Discoverability of operations</li>\n<li>Potential code duplication</li>\n<li>Lack of encapsulation</li>\n</ul>\n<p>We'll apply a few techniques to push logic down into the domain, and try to make the model more domain-driven.\nI hope you'll be able to see the value and benefits this will bring.</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; Handle(SendInvitationCommand command)\n{\n    var member = await _memberRepository.GetByIdAsync(command.MemberId);\n\n    var gathering = await _gatheringRepository.GetByIdAsync(command.GatheringId);\n\n    if (member is null || gathering is null)\n    {\n        return Result.Failure(Error.NullValue);\n    }\n\n    if (gathering.Creator.Id == member.Id)\n    {\n        throw new Exception(&quot;Can't send invitation to the creator.&quot;);\n    }\n\n    if (gathering.ScheduledAtUtc &lt; DateTime.UtcNow)\n    {\n        throw new Exception(&quot;Can't send invitation for the past.&quot;);\n    }\n\n    var invitation = new Invitation\n    {\n        Id = Guid.NewGuid(),\n        Member = member,\n        Gathering = gathering,\n        Status = InvitationStatus.Pending,\n        CreatedOnUtc = DateTime.UtcNow\n    };\n\n    gathering.Invitations.Add(invitation);\n\n    _invitationRepository.Add(invitation);\n\n    await _unitOfWork.SaveChangesAsync();\n\n    await _emailService.SendInvitationSentEmailAsync(member, gathering);\n\n    return Result.Success();\n}\n</code></pre>\n<h2>Moving Business Logic Into The Domain</h2>\n<p>The goal is to move as much of the business logic as possible into the domain.</p>\n<p>Let's start with the <code>Invitation</code> entity and defining a constructor for it.\nI can simplify the design by setting the <code>Status</code> and <code>CreatedOnUtc</code> properties inside the constructor.\nI'm also going to make it <code>internal</code> so that an <code>Invitation</code> instance can only be created within the domain.</p>\n<pre><code class=\"language-csharp\">public sealed class Invitation\n{\n    internal Invitation(Guid id, Gathering gathering, Member member)\n    {\n        Id = id;\n        Member = member;\n        Gathering = gathering;\n        Status = InvitationStatus.Pending;\n        CreatedOnUtc = DateTime.Now;\n    }\n\n    // Data properties omitted for brevity.\n}\n</code></pre>\n<p>The reason I made the <code>Invitation</code> constructor <code>internal</code> is so that I can introduce a new method on the <code>Gathering</code> entity.\nLet's call it <code>SendInvitation</code> and it will be responsible for instantiating a new <code>Invitation</code> instance and adding it to the internal collection.</p>\n<p>Currently, the <code>Gathering.Invitations</code> collection is <code>public</code>, which means anyone can obtain a reference and modify the collection.</p>\n<p>We don't want to allow this, so what we can do is encapsulate this collection behind a <code>private</code> field.\nThis moves the responsibility for managing the <code>_invitations</code> collection to the <code>Gathering</code> class.</p>\n<p>Here's how the <code>Gathering</code> class looks like now:</p>\n<pre><code class=\"language-csharp\">public sealed class Gathering\n{\n    private readonly List&lt;Invitation&gt; _invitations;\n\n    // Other members omitted for brevity.\n\n    public void SendInvitation(Member member)\n    {\n        var invitation = new Invitation(Guid.NewGuid(), gathering, member);\n\n        _invitations.Add(invitation);\n    }\n}\n</code></pre>\n<h2>Moving Validation Rules Into The Domain</h2>\n<p>The next thing we can do is move the validation rules into the <code>SendInvitation</code> method, further enriching the domain model.</p>\n<p>Unfortunately, this is still a bad practice because of throwing &quot;expected&quot; exceptions when a validation fails.\nIf you want to use exceptions to enforce your validation rules you should at least do it right, and use specific exceptions instead of generic ones.</p>\n<p>But it would be even better to use a <strong>result object</strong> to express validation errors.</p>\n<pre><code class=\"language-csharp\">public sealed class Gathering\n{\n    // Other members omitted for brevity.\n\n    public void SendInvitation(Member member)\n    {\n        if (gathering.Creator.Id == member.Id)\n        {\n            throw new Exception(&quot;Can't send invitation to the creator.&quot;);\n        }\n\n        if (gathering.ScheduledAtUtc &lt; DateTime.UtcNow)\n        {\n            throw new Exception(&quot;Can't send invitation for the past.&quot;);\n        }\n\n        var invitation = new Invitation(Guid.NewGuid(), gathering, member);\n\n        _invitations.Add(invitation);\n    }\n}\n</code></pre>\n<p>Here's how using <strong>result objects</strong> would look like:</p>\n<pre><code class=\"language-csharp\">public sealed class Gathering\n{\n    // Other members omitted for brevity.\n\n    public Result SendInvitation(Member member)\n    {\n        if (gathering.Creator.Id == member.Id)\n        {\n            return Result.Failure(DomainErrors.Gathering.InvitingCreator);\n        }\n\n        if (gathering.ScheduledAtUtc &lt; DateTime.UtcNow)\n        {\n            return Result.Failure(DomainErrors.Gathering.AlreadyPassed);\n        }\n\n        var invitation = new Invitation(Guid.NewGuid(), gathering, member);\n\n        _invitations.Add(invitation);\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>The benefit of this approach is we can introduce constants for possible domain errors.\nThe catalog of domain errors will act as <strong>documentation</strong> for your domain, and make it more expressive.</p>\n<p>Finally, here's how the <code>Handle</code> method looks like with all the changes so far:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; Handle(SendInvitationCommand command)\n{\n    var member = await _memberRepository.GetByIdAsync(command.MemberId);\n\n    var gathering = await _gatheringRepository.GetByIdAsync(command.GatheringId);\n\n    if (member is null || gathering is null)\n    {\n        return Result.Failure(Error.NullValue);\n    }\n\n    var result = gathering.SendInvitation(member);\n\n    if (result.IsFailure)\n    {\n        return Result.Failure(result.Errors);\n    }\n\n    await _unitOfWork.SaveChangesAsync();\n\n    await _emailService.SendInvitationSentEmailAsync(member, gathering);\n\n    return Result.Success();\n}\n</code></pre>\n<p>If you take a closer look at the <code>Handle</code> method you'll notice it's doing two things:</p>\n<ul>\n<li>Persisting changes to the database</li>\n<li>Sending an email</li>\n</ul>\n<p>This means it's <strong>not atomic</strong>.</p>\n<p>There's a potential for the database transaction to complete, and the email sending to fail.\nAlso, sending the email will slow down the method which could affect performance.</p>\n<p>How can make this method atomic?</p>\n<p>By sending the email in the background. It's not important for our business logic, so this is safe to do.</p>\n<h2>Expressing Side Effects With Domain Events</h2>\n<p>You can use <a href=\"https://milanjovanovic.tech/blog/domain-events-vs-integration-events\"><strong>domain events</strong></a> to express that something occurred in your domain that might be interesting to other components in your system.</p>\n<p>I often use <strong>domain events</strong> to trigger actions in the background, like sending a notification or email.</p>\n<p>Let's introduce an <code>InvitationSentDomainEvent</code>:</p>\n<pre><code class=\"language-csharp\">public record InvitationSentDomainEvent(Invitation Invitation) : IDomainEvent;\n</code></pre>\n<p>We're going to raise this <strong>domain event</strong> inside the <code>SendInvitation</code> method:</p>\n<pre><code class=\"language-csharp\">public sealed class Gathering\n{\n    private readonly List&lt;Invitation&gt; _invitations;\n\n    // Other members omitted for brevity.\n\n    public Result SendInvitation(Member member)\n    {\n        if (gathering.Creator.Id == member.Id)\n        {\n            return Result.Failure(DomainErrors.Gathering.InvitingCreator);\n        }\n\n        if (gathering.ScheduledAtUtc &lt; DateTime.UtcNow)\n        {\n            return Result.Failure(DomainErrors.Gathering.AlreadyPassed);\n        }\n\n        var invitation = new Invitation(Guid.NewGuid(), gathering, member);\n\n        _invitations.Add(invitation);\n\n        Raise(new InvitationSentDomainEvent(invitation));\n\n        return Result.Success();\n    }\n}\n</code></pre>\n<p>The goal is to remove the code responsible for sending the email from the <code>Handle</code> method:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;Result&gt; Handle(SendInvitationCommand command)\n{\n    var member = await _memberRepository.GetByIdAsync(command.MemberId);\n\n    var gathering = await _gatheringRepository.GetByIdAsync(command.GatheringId);\n\n    if (member is null || gathering is null)\n    {\n        return Result.Failure(Error.NullValue);\n    }\n\n    var result = gathering.SendInvitation(member);\n\n    if (result.IsFailure)\n    {\n        return Result.Failure(result.Errors);\n    }\n\n    await _unitOfWork.SaveChangesAsync();\n\n    return Result.Success();\n}\n</code></pre>\n<p>We only want to worry about executing the business logic and persisting any changes to the database.\nPart of those changes will also be the <strong>domain event</strong>, which the system will publish in the background.</p>\n<p>Of course, we need a respective <strong>handler</strong> for the <strong>domain event</strong>:</p>\n<pre><code class=\"language-csharp\">public sealed class InvitationSentDomainEventHandler\n    : IDomainEventHandler&lt;InvitationSentDomainEvent&gt;\n{\n    private readonly IEmailService _emailService;\n\n    public InvitationSentDomainEventHandler(IEmailService emailService)\n    {\n        _emailService = emailService;\n    }\n\n    public async Task Handle(InvitationSentDomainEvent domainEvent)\n    {\n        await _emailService.SendInvitationSentEmailAsync(\n            domainEvent.Invitation.Member,\n            domainEvent.Invitation.Gathering);\n    }\n}\n</code></pre>\n<p>We achieved two things:</p>\n<ul>\n<li>Handling the <code>SendInvitationCommand</code> is now atomic</li>\n<li>Email is sent in the background, and can be safely retried in case of an error</li>\n</ul>\n<h2>Takeaway</h2>\n<p>Designing a <strong>rich domain model</strong> is a gradual process, and you can slowly evolve the domain model over time.</p>\n<p>The first step could be making your domain model more defensive:</p>\n<ul>\n<li>Hiding constructors with the <code>internal</code> keyword</li>\n<li><strong>Encapsulating collection access</strong></li>\n</ul>\n<p>The benefit is your domain models will have a fine-grained public API (methods) which act as an entry point for executing the business logic.</p>\n<p>It's easy to test behavior when it's encapsulated in a class without having to mock external dependencies.</p>\n<p>You can raise <strong>domain events</strong> to notify the system that something of important occurred, and any interested components can subscribe to that domain event.\nDomain events allow you to develop a <strong>decoupled</strong> system, where you focus on the core domain logic, and don't have to worry about the side effects.</p>\n<p>However, this doesn't mean that every system needs a <strong>rich domain model</strong>.</p>\n<p>You should be pragmatic and decide when the complexity is worth it.</p>\n<p>That's all for this week.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/refactoring-from-an-anemic-domain-model-to-a-rich-domain-model",
            "title": "Refactoring From an Anemic Domain Model To a Rich Domain Model",
            "summary": "Is the anemic domain model an antipattern? It works great in simple applications, but with rich business logic the important rules end up scattered all over…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_042.png",
            "date_modified": "2023-06-17T00:00:00.000Z",
            "date_published": "2023-06-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet",
            "content_html": "<p>The right way to use <code>HttpClient</code> in .NET is to avoid creating an instance per request (port exhaustion) and to avoid a naive singleton (stale DNS).\nLet <code>IHttpClientFactory</code> manage the lifetime for you, or reuse a single instance with a configured <code>PooledConnectionLifetime</code>.\nThis issue covers named clients, typed clients, and when to use each option.</p>\n<p>If you're building a <strong>.NET</strong> application, chances are high that you'll need to call an <strong>external API</strong> over <strong>HTTP</strong>.</p>\n<p>The easy way to make HTTP requests in .NET is to use the <code>HttpClient</code> to send those requests.\nAnd it's a great abstraction to work with, especially with the methods supporting <strong>JSON</strong> payloads and responses.</p>\n<p>Unfortunately, it's easy to misuse the <code>HttpClient</code>.</p>\n<p><strong>Port exhaustion</strong> and <strong>DNS behavior</strong> are some of the most common problems.</p>\n<p>So here's what you need to know about working with <code>HttpClient</code>:</p>\n<ul>\n<li>How not to use <code>HttpClient</code></li>\n<li>How to simplify configuration with <code>IHttpClientFactory</code></li>\n<li>How to configure <strong>typed clients</strong></li>\n<li>Why you should avoid <strong>typed clients</strong> in singleton services</li>\n<li>When to use which option</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>What Is HttpClient in .NET?</h2>\n<p><code>HttpClient</code> is the primary class in .NET for sending HTTP requests and receiving HTTP responses from a URI.\nIt lives in the <code>System.Net.Http</code> namespace and is included in the .NET runtime (you don't need extra packages).</p>\n<p>You can use it to call REST APIs, download files, or communicate with any HTTP-based service.\nIt supports all standard HTTP methods (GET, POST, PUT, DELETE, PATCH) and has built-in support for JSON\nthrough extension methods like <code>GetFromJsonAsync</code> and <code>PostAsJsonAsync</code>.</p>\n<p>While <code>HttpClient</code> is easy to get started with, it's also easy to misuse.\nThe most common mistakes are creating too many instances (causing port exhaustion) and holding on to instances for too long (causing stale DNS responses).\nThe rest of this article covers the right patterns for working with <code>HttpClient</code> in .NET.</p>\n<h2>The Naive Way To Use HttpClient</h2>\n<p>The simplest way to work with the <code>HttpClient</code> is to just create a new instance, set the required properties and use it to send requests.</p>\n<p>What could possibly go wrong?</p>\n<p><code>HttpClient</code> instances are meant to be <strong>long-lived</strong>, and reused throughout the lifetime of the application.</p>\n<p>Each instance uses its own <strong>connection pool</strong> for isolation purposes, but also to prevent <strong>port exhaustion</strong>.\nIf a server is under high load, and your application is constantly creating new connections, it could lead to exhausting the available ports.\nThis will cause an exception at runtime, when trying to send a request.</p>\n<p>So how can you avoid this?</p>\n<pre><code class=\"language-csharp\">public class GitHubService\n{\n    private readonly GitHubSettings _settings;\n\n    public GitHubService(IOptions&lt;GitHubSettings&gt; settings)\n    {\n        _settings = settings.Value;\n    }\n\n    public async Task&lt;GitHubUser?&gt; GetUserAsync(string username)\n    {\n        using var client = new HttpClient();\n\n        client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, _settings.GitHubToken);\n        client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, _settings.UserAgent);\n        client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n\n        GitHubUser? user = await client\n            .GetFromJsonAsync&lt;GitHubUser&gt;($&quot;users/{username}&quot;);\n\n        return user;\n    }\n}\n</code></pre>\n<h2>The Smart Way To Create HttpClient Using IHttpClientFactory</h2>\n<p>Instead of managing the <code>HttpClient</code> lifetime yourself, you can use an <strong><code>IHttpClientFactory</code></strong> to create the <code>HttpClient</code> instance.</p>\n<p>Simply call the <code>CreateClient</code> method and use the returned <code>HttpClient</code> instance to send your HTTP requests.</p>\n<p>Why is this a better approach?</p>\n<p>The expensive part of the <code>HttpClient</code> is the actual message handler - <code>HttpMessageHandler</code>.\nEach <code>HttpMessageHandler</code> has an internal HTTP <strong>connection pool</strong> that can be reused.</p>\n<p>The <code>IHttpClientFactory</code> will <strong>cache</strong> the <code>HttpMessageHandler</code> and reuse it when creating a new <code>HttpClient</code> instance.</p>\n<p>An important note here is that <code>HttpClient</code> instances created by <code>IHttpClientFactory</code> are meant to be <strong>short-lived</strong>.</p>\n<pre><code class=\"language-csharp\">public class GitHubService\n{\n    private readonly GitHubSettings _settings;\n    private readonly IHttpClientFactory _factory;\n\n    public GitHubService(\n        IOptions&lt;GitHubSettings&gt; settings,\n        IHttpClientFactory factory)\n    {\n        _settings = settings.Value;\n        _factory = factory;\n    }\n\n    public async Task&lt;GitHubUser?&gt; GetUserAsync(string username)\n    {\n        using var client = _factory.CreateClient();\n\n        client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, _settings.GitHubToken);\n        client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, _settings.UserAgent);\n        client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n\n        GitHubUser? user = await client\n            .GetFromJsonAsync&lt;GitHubUser&gt;($&quot;users/{username}&quot;);\n\n        return user;\n    }\n}\n</code></pre>\n<h2>Reducing Code Duplication With Named Clients</h2>\n<p>Using <code>IHttpClientFactory</code> will solve most of the issues of manually creating an <code>HttpClient</code>.\nHowever, we still need to configure the default request parameters every time we obtain a new <code>HttpClient</code> from the <code>CreateClient</code> method.</p>\n<p>You can configure a <strong>named client</strong> by calling the <code>AddHttpClient</code> method and passing in the desired name.\nThe <code>AddHttpClient</code> accepts a delegate that you can use to configure the default parameters on the <code>HttpClient</code> instance.</p>\n<pre><code class=\"language-csharp\">services.AddHttpClient(&quot;github&quot;, (serviceProvider, client) =&gt;\n{\n    var settings = serviceProvider\n        .GetRequiredService&lt;IOptions&lt;GitHubSettings&gt;&gt;().Value;\n\n    client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, settings.GitHubToken);\n    client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, settings.UserAgent);\n\n    client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n});\n</code></pre>\n<p>The main difference is you now have to obtain the client by passing the name of the client to <code>CreateClient</code>.</p>\n<p>But the use of the <code>HttpClient</code> looks a lot simpler:</p>\n<pre><code class=\"language-csharp\">public class GitHubService\n{\n    private readonly IHttpClientFactory _factory;\n\n    public GitHubService(IHttpClientFactory factory)\n    {\n        _factory = factory;\n    }\n\n    public async Task&lt;GitHubUser?&gt; GetUserAsync(string username)\n    {\n        using var client = _factory.CreateClient(&quot;github&quot;);\n\n        GitHubUser? user = await client\n            .GetFromJsonAsync&lt;GitHubUser&gt;($&quot;users/{username}&quot;);\n\n        return user;\n    }\n}\n</code></pre>\n<h2>Replacing Named Clients With Typed Clients</h2>\n<p>The downside of using <strong>named clients</strong> is having to resolve an <code>HttpClient</code> by passing in a name every time.</p>\n<p>There's a better way to achieve the same behavior by configuring a <strong>typed client</strong>.\nYou can do this by calling the <code>AddClient&lt;TClient&gt;</code> method and configuring the service that will consume the <code>HttpClient</code>.</p>\n<p>Under the hood, this is still using a <strong>named client</strong>, where the name is the same as the type name.</p>\n<p>And this will also register <code>GitHubService</code> with a <strong>transient lifetime</strong>.</p>\n<pre><code class=\"language-csharp\">services.AddHttpClient&lt;GitHubService&gt;((serviceProvider, client) =&gt;\n{\n    var settings = serviceProvider\n        .GetRequiredService&lt;IOptions&lt;GitHubSettings&gt;&gt;().Value;\n\n    client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, settings.GitHubToken);\n    client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, settings.UserAgent);\n\n    client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n});\n</code></pre>\n<p>Inside of <code>GitHubService</code>, you inject and use the typed <code>HttpClient</code> instance which will have all of the configuration applied.</p>\n<p>No more dealing with <code>IHttpClientFactory</code> and creating <code>HttpClient</code> instances manually.</p>\n<pre><code class=\"language-csharp\">public class GitHubService\n{\n    private readonly HttpClient client;\n\n    public GitHubService(HttpClient client)\n    {\n        _client = client;\n    }\n\n    public async Task&lt;GitHubUser?&gt; GetUserAsync(string username)\n    {\n        GitHubUser? user = await client\n            .GetFromJsonAsync&lt;GitHubUser&gt;($&quot;users/{username}&quot;);\n\n        return user;\n    }\n}\n</code></pre>\n<h2>Why You Should Avoid Typed Clients In Singleton Services</h2>\n<p>You could run into a <strong>problem</strong> if you inject a <strong>typed client</strong> into a <strong>singleton service</strong>.\nSince the <strong>typed client</strong> is <strong>transient</strong>, injecting it in a <strong>singleton service</strong> will cause it to be cached for the lifetime of the <strong>singleton service</strong>.</p>\n<p>This will prevent the <strong>typed client</strong> from reacting to DNS changes.</p>\n<p>If you want to use a <strong>typed client</strong> in a <strong>singleton service</strong>, the recommened approach is using <code>SocketsHttpHandler</code> as the primary handler,\nand configuring the <code>PooledConnectionLifetime</code>.</p>\n<p>Since the <code>SocketsHttpHandler</code> will handle connection pooling, you can disable recycling at the <code>IHttpClientFactory</code> level by setting <code>HandlerLifetime</code> to <code>Timeout.InfiniteTimeSpan</code>.</p>\n<pre><code class=\"language-csharp\">services.AddHttpClient&lt;GitHubService&gt;((serviceProvider, client) =&gt;\n{\n    var settings = serviceProvider\n        .GetRequiredService&lt;IOptions&lt;GitHubSettings&gt;&gt;().Value;\n\n    client.DefaultRequestHeaders.Add(&quot;Authorization&quot;, settings.GitHubToken);\n    client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, settings.UserAgent);\n\n    client.BaseAddress = new Uri(&quot;https://api.github.com&quot;);\n})\n.ConfigurePrimaryHttpMessageHandler(() =&gt;\n{\n    return new SocketsHttpHandler()\n    {\n        PooledConnectionLifetime = TimeSpan.FromMinutes(15)\n    };\n})\n.SetHandlerLifetime(Timeout.InfiniteTimeSpan);\n</code></pre>\n<h2>SocketsHttpHandler vs HttpClientHandler</h2>\n<p>Under the hood, every <code>HttpClient</code> uses an <code>HttpMessageHandler</code> to manage TCP connections.\n.NET has two primary handler implementations worth knowing.</p>\n<p><strong><code>HttpClientHandler</code></strong> is the traditional handler, available since .NET Framework.\nOn modern .NET runtimes it internally delegates to <code>SocketsHttpHandler</code>.\nIt exposes familiar properties for cookies, automatic decompression, and proxy configuration.</p>\n<p><strong><code>SocketsHttpHandler</code></strong> is the fully managed, cross-platform handler that debuted in .NET Core 2.1 and is now the default on all current .NET runtimes.\nUnlike <code>HttpClientHandler</code>, it exposes <code>PooledConnectionLifetime</code> and <code>PooledConnectionIdleTimeout</code>,\ngiving you direct control over how long connections stay in the pool before being recycled.\nThis is exactly what makes <code>SocketsHttpHandler</code> valuable when using a typed <code>HttpClient</code> in a singleton service, as\nyou can ensure DNS changes are picked up without relying on <code>IHttpClientFactory</code> handler recycling.</p>\n<p>For most applications using <code>IHttpClientFactory</code>, you don't need to choose explicitly.\nConfigure <code>SocketsHttpHandler</code> directly only when you need fine-grained control over connection-pool behavior.\nYou wire it up with <code>ConfigurePrimaryHttpMessageHandler</code> when registering the client:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddHttpClient&lt;MyService&gt;()\n    .ConfigurePrimaryHttpMessageHandler(() =&gt; new SocketsHttpHandler\n    {\n        PooledConnectionLifetime = TimeSpan.FromMinutes(2)\n    });\n</code></pre>\n<h2>When Should You Use Which Option?</h2>\n<p>I showed you a few possible options for working with <code>HttpClient</code>.</p>\n<p>But which one should you use and when?</p>\n<p>Microsoft was kind enough to provide us with a set of best practices and <a href=\"https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use\">recommended use</a>\nfor <code>HttpClient</code>.</p>\n<ul>\n<li>Use a <code>static</code> or <strong>singleton</strong> <code>HttpClient</code> instance with a <code>PooledConnectionLifetime</code> configured, since this solves both port exhaustion and tracking DNS changes</li>\n<li>Use <code>IHttpClientFactory</code> if you want to move the configuration to one place, but remember that clients are meant to be <strong>short-lived</strong></li>\n<li>Use a <strong>typed client</strong> if you want the <code>IHttpClientFactory</code> configurability</li>\n</ul>\n<p>I prefer working with a <strong>typed client</strong>, and I'm mindful of the fact that it's configured as a <strong>transient service</strong>.</p>\n<p>Thanks for reading, and have an awesome Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet",
            "title": "The Right Way To Use HttpClient In .NET",
            "summary": "The easy way to make HTTP requests in .NET is HttpClient. Unfortunately, it's easy to misuse, and port exhaustion and DNS behavior are some of the most common…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_041.png",
            "date_modified": "2023-06-10T00:00:00.000Z",
            "date_published": "2023-06-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net",
            "content_html": "<p>Quartz.NET is an open source job scheduling system for .NET that runs recurring background work.\nIt has three concepts: the job you want to run, the trigger controlling when it runs, and the scheduler coordinating them.\nYou install <code>Quartz.Extensions.Hosting</code>, implement <code>IJob</code>, and register a trigger with a simple schedule or a cron expression.</p>\n<p>If you're building a scalable application, it's a common requirement to offload some work in your application to a <strong>background job</strong>.</p>\n<p>Here are a few examples of that:</p>\n<ul>\n<li>Publishing email notifications</li>\n<li>Generating reports</li>\n<li>Updating a cache</li>\n<li>Image processing</li>\n</ul>\n<p>How can you create a recurring <strong>background job</strong> in .NET?</p>\n<p><a href=\"https://www.quartz-scheduler.net/\"><strong>Quartz.NET</strong></a> is a full-featured, open source job scheduling system that can be used from smallest apps to large scale enterprise systems.</p>\n<p>There are three concepts you need to understand in <strong>Quartz.NET</strong>:</p>\n<ul>\n<li><strong>Job</strong> - the actual background task you want to run</li>\n<li><strong>Trigger</strong> - the trigger controlling when a job runs</li>\n<li><strong>Scheduler</strong> - responsible for coordinating jobs and triggers</li>\n</ul>\n<p>Let's see how we can use <strong>Quartz.NET</strong> to create and schedule <strong>background jobs</strong>.</p>\n<h2>Adding The Quartz.NET Hosted Service</h2>\n<p>The first thing we need to do is install the <strong>Quartz.NET</strong> NuGet package.\nThere are a few to pick from, but we're going to install the <code>Quartz.Extensions.Hosting</code> library:</p>\n<pre><code class=\"language-powershell\">Install-Package Quartz.Extensions.Hosting\n</code></pre>\n<p>The reason we're using this library is because it integrates nicely with .NET using an <code>IHostedService</code> instance.</p>\n<p>To get the <strong>Quartz.NET</strong> hosted service up and running, we need two things:</p>\n<ul>\n<li>Add the required services with the DI container</li>\n<li>Add the hosted service</li>\n</ul>\n<pre><code class=\"language-csharp\">services.AddQuartz(configure =&gt;\n{\n    configure.UseMicrosoftDependencyInjectionJobFactory();\n});\n\nservices.AddQuartzHostedService(options =&gt;\n{\n    options.WaitForJobsToComplete = true;\n});\n</code></pre>\n<p><strong>Quartz.NET</strong> will create jobs by fetching them from the DI container.\nThis also means you can use <a href=\"https://milanjovanovic.tech/blog/using-scoped-services-from-singletons-in-aspnetcore\"><strong>scoped services</strong></a> in your jobs, not just singleton or transient services.</p>\n<p>Setting the <code>WaitForJobsToComplete</code> option to <code>true</code> will ensure that <strong>Quartz.NET</strong> waits for the jobs to complete gracefully before exiting.</p>\n<h2>Creating Background Jobs With <code>IJob</code></h2>\n<p>To crate a background job with <strong>Quartz.NET</strong> you need to implement the <code>IJob</code> interface.</p>\n<p>It only exposes a single method - <code>Execute</code> - where you can place the code for your background job.</p>\n<p>A few things worth noting here:</p>\n<ul>\n<li>We're using DI to inject the <code>ApplicationDbContext</code> and <code>IPublisher</code> services</li>\n<li>The job is decorated with <code>DisallowConcurrentExecution</code> to prevent running the same job concurrently</li>\n</ul>\n<pre><code class=\"language-csharp\">[DisallowConcurrentExecution]\npublic class ProcessOutboxMessagesJob : IJob\n{\n    private readonly ApplicationDbContext _dbContext;\n    private readonly IPublisher _publisher;\n\n    public ProcessOutboxMessagesJob(\n        ApplicationDbContext dbContext,\n        IPublisher publisher)\n    {\n        _dbContext = dbContext;\n        _publisher = publisher;\n    }\n\n    public async Task Execute(IJobExecutionContext context)\n    {\n        List&lt;OutboxMessage&gt; messages = await _dbContext\n            .Set&lt;OutboxMessage&gt;()\n            .Where(m =&gt; m.ProcessedOnUtc == null)\n            .Take(20)\n            .ToListAsync(context.CancellationToken);\n\n        foreach (OutboxMessage outboxMessage in messages)\n        {\n            IDomainEvent? domainEvent = JsonConvert\n                .DeserializeObject&lt;IDomainEvent&gt;(\n                    outboxMessage.Content,\n                    new JsonSerializerSettings\n                    {\n                        TypeNameHandling = TypeNameHandling.All\n                    });\n\n            if (domainEvent is null)\n            {\n                continue;\n            }\n\n            await _publisher.Publish(domainEvent, context.CancellationToken);\n\n            outboxMessage.ProcessedOnUtc = DateTime.UtcNow;\n\n            await _dbContext.SaveChangesAsync();\n        }\n    }\n}\n</code></pre>\n<p>Now that the <strong>background job</strong> is ready, we need to register it with the <strong>DI</strong> container and add a trigger that will run the job.</p>\n<h2>Configuring the Job</h2>\n<p>I mentioned at the start that there are three key concepts in <strong>Quartz.NET</strong>:</p>\n<ul>\n<li>Job</li>\n<li>Trigger</li>\n<li>Scheduler</li>\n</ul>\n<p>We already implemented the <code>ProcessOutboxMessagesJob</code> background job in the previous section.</p>\n<p>The <strong>Quartz.NET</strong> library will take care of the scheduler.</p>\n<p>And this leaves us with configuring the <strong>trigger</strong> for our <code>ProcessOutboxMessagesJob</code>.</p>\n<pre><code class=\"language-csharp\">services.AddQuartz(configure =&gt;\n{\n    var jobKey = new JobKey(nameof(ProcessOutboxMessagesJob));\n\n    configure\n        .AddJob&lt;ProcessOutboxMessagesJob&gt;(jobKey)\n        .AddTrigger(\n            trigger =&gt; trigger.ForJob(jobKey).WithSimpleSchedule(\n                schedule =&gt; schedule.WithIntervalInSeconds(10).RepeatForever()));\n\n    configure.UseMicrosoftDependencyInjectionJobFactory();\n});\n</code></pre>\n<p>We need to uniquely identify our <strong>background job</strong> with a <code>JobKey</code>.\nI like to keep it simple and use the job name.</p>\n<p>Calling <code>AddJob</code> will register the <code>ProcessOutboxMessagesJob</code> with DI and also with Quartz.</p>\n<p>After that we configure a trigger for this job by calling <code>AddTrigger</code>.\nYou need to associate the job with the trigger by calling <code>ForJob</code>, and then you configure the schedule for the background job.\nIn this example, I'm scheduling the job to run every ten seconds and repeat forever while the hosted service is running.</p>\n<p><strong>Quartz</strong> also has support for configuring triggers using <a href=\"https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontriggers.html\">cron expressions.</a></p>\n<h2>Job Persistence</h2>\n<p>By default, Quartz configures all jobs using the <code>RAMJobStore</code> which is the most performant because it keeps all of its data in RAM.\nHowever, this also means it's volatile and you can lose all scheduling information when your application stops or crashes.</p>\n<p>It could be useful to have a persistent job store in some scenarios and there's a built in <code>AdoJobStore</code> which works with SQL databases.\nYou need to create a set of database tables for Quartz.NET to use.</p>\n<p>You can learn more about this in the <a href=\"https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/job-stores.html\">job stores documentation.</a></p>\n<h2>Takeaway</h2>\n<p><strong>Quartz.NET</strong> makes running <strong>background jobs</strong> in .NET easy, and you can use all the power of DI in your <strong>background jobs</strong>.\nIt's also flexible for various scheduling requirements with configuration via code or using cron expressions.</p>\n<p>There's some room for improvement to make scheduling jobs easier and reduce boilerplate:</p>\n<ul>\n<li>Add an extension method to simplify configuring jobs with a simple schedule</li>\n<li>Add an extension method to simplify configuring jobs with a cron schedule from application settings</li>\n</ul>\n<p>If you want to see a tutorial on using <strong>Quartz.NET</strong>, I made an in-depth video about <a href=\"https://youtu.be/XALvnX7MPeo\"><strong>using Quartz for processing Outbox messages</strong></a>.</p>\n<p>That's all for this week.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-net",
            "title": "Scheduling Background Jobs With Quartz.NET",
            "summary": "How can you create a recurring background job in .NET? Quartz.NET is a full-featured, open source job scheduling system that can be used from smallest apps to…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_040.png",
            "date_modified": "2023-06-03T00:00:00.000Z",
            "date_published": "2023-06-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet",
            "content_html": "<p>A GitHub Actions workflow builds, tests, and deploys a .NET application on every push to <code>main</code>.\nThe build job checks out the code, sets up the .NET SDK, then runs <code>dotnet restore</code>, <code>dotnet build</code>, and <code>dotnet test</code>.\nThe deployment job adds a <code>dotnet publish</code> step and the <code>azure/webapps-deploy</code> action to ship the output to Azure App Service.</p>\n<p>Do you want to streamline your software development process and accelerate your release cycles?</p>\n<p>Imagine being able to automatically build, test, and deploy your .NET applications with every code change.</p>\n<p>With <strong>CI/CD</strong>, you can significantly reduce manual effort and focus more on creating software, ensuring faster and more reliable releases.</p>\n<p>And it's never been easier to get started with <strong>CI/CD</strong>.</p>\n<p><strong>GitHub Actions</strong> are completely free and simple to use.</p>\n<p>So here's what we'll cover:</p>\n<ul>\n<li>Introduction to <strong>CI/CD</strong> &amp; <strong>GitHub Actions</strong></li>\n<li>Creating a <strong>build &amp; test pipeline</strong> for <strong>.NET</strong></li>\n<li>Creating a <strong>deployment pipeline</strong> for <strong>Azure App Service</strong></li>\n</ul>\n<p>Let's dive in.</p>\n<h2>What Is Continuous Integration And Delivery?</h2>\n<p>I'll try to briefly explain what <strong>CI/CD</strong> is, before we take a look at <strong>GitHub Actions</strong>.</p>\n<p><strong>CI/CD</strong> is a method to increase the frequency of delivering new features by adding automation to your software development workflow.</p>\n<p><strong>Continuous Integration (&quot;CI&quot;)</strong> refers to the automation process of syncing new code to a repository.\nAny new changes to the application code are immediately built, tested and merged.</p>\n<p><strong>Continuous Delivery, or Deployment, (&quot;CD&quot;)</strong> refers to the process of automating the deployment part of the workflow.\nWhen you make a change which gets merged to the repository, this step takes care of deploying those changes to the production environment (or any other environment).</p>\n<h2>Continuous Integration With GitHub Actions</h2>\n<p>If you're using <strong>GitHub</strong>, getting started with <strong>Continuous Integration</strong> has never been easier.</p>\n<p>You can use <a href=\"https://github.com/features/actions\"><strong>GitHub Actions</strong></a> to automate your build, test, and deployment <strong>pipeline</strong>.\nYou can create workflows that build and test every commit to your repository, or deploy to production when a new tag is created.</p>\n<p>To create a <strong>GitHub Action</strong>, you write a <em>workflow</em> to be triggered when some <em>event</em> occurs in your repository.\nAn example event is a commit to the main branch, creation of a tag, or you can manually run the workflow.</p>\n<p>Here's a <strong>GitHub Actions</strong> workflow to build and test a .NET project:</p>\n<pre><code class=\"language-yaml\">name: Build &amp; Test 🧪\n\non:\n  push:\n    branches:\n      - main\n\nenv:\n  DOTNET_VERSION: '7.0.x'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup .NET 📦\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: ${{ env.DOTNET_VERSION }}\n\n      - name: Install dependencies 📂\n        run: dotnet restore WebApi\n\n      - name: Build 🧱\n        run: dotnet build WebApi --configuration Release --no-restore\n\n      - name: Test 🧪\n        run: dotnet test WebApi --configuration Release --no-build\n</code></pre>\n<p>Let's unwrap what is happening here:</p>\n<ul>\n<li>Defining an event to trigger the workflow</li>\n<li>Setting up the <strong>.NET SDK</strong> with the version from <code>env.DOTNET_VERSION</code></li>\n<li>Restoring, building and testing the project using the <code>dotnet</code> CLI tool</li>\n</ul>\n<p>You can add this to your <strong>GitHub</strong> repository today, and start getting instant feedback when you commit code to the repository.</p>\n<p>When a workflow run fails due to a build error or a failed test, you'll get an email notification.</p>\n<h2>Continuous Delivery To Azure With GitHub Actions</h2>\n<p><strong>Continuous Integration</strong> is a great way to start out with <strong>CI/CD</strong>, but the real value lies in <strong>automating</strong> your <strong>deployment</strong> process.</p>\n<p>Imagine this:</p>\n<ul>\n<li>You make a change to your codebase</li>\n<li>The commit triggers the <strong>deployment pipeline</strong></li>\n<li>A few minutes later your changes are live in production</li>\n</ul>\n<p>Usually it's a little more nuanced, because we need to think about configuration, <a href=\"https://milanjovanovic.tech/blog/efcore-migrations-a-detailed-guide\"><strong>database migrations</strong></a>, etc.\nBut try to see the big picture here.</p>\n<p>If you're running your application in the cloud, for example on <strong>Azure</strong>, chances are there's an existing <strong>GitHub Action</strong> you can use.</p>\n<p>Here's a <strong>deployment pipeline</strong> I use to publish my application to an <strong>Azure App Service</strong> instance:</p>\n<pre><code class=\"language-yaml\">name: Publish 🚀\n\non:\n  push:\n    branches:\n      - main\n\nenv:\n  AZURE_WEBAPP_NAME: web-api\n  AZURE_WEBAPP_PACKAGE_PATH: './publish'\n  DOTNET_VERSION: '7.0.x'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup .NET 📦\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: ${{ env.DOTNET_VERSION }}\n\n      - name: Build and Publish 📂\n        run: |\n          dotnet restore WebApi\n          dotnet build WebApi -c Release --no-restore\n          dotnet publish WebApi -c Release --no-build\n            --output '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'\n\n      - name: Deploy to Azure 🌌\n        uses: azure/webapps-deploy@v2\n        with:\n          app-name: ${{ env.AZURE_WEBAPP_NAME }}\n          publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}\n          package: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'\n</code></pre>\n<p>This workflow is pretty similar to the previous one, with the differences being:</p>\n<ul>\n<li>Adding a publish step and configuring the output path</li>\n<li>Using the <code>azure/webapps-deploy@v2</code> action to deploy to <strong>Azure</strong></li>\n</ul>\n<p>If you need to safely and securely expose secret values in your workflows, you can use <strong>GitHub secrets</strong>.\nYou can define the secrets on GitHub, and use them in actions without having to add them to source control.</p>\n<p>In the deployment workflow I'm using <code>secrets.AZURE_PUBLISH_PROFILE</code> to access my publish profile for the App Service instance.</p>\n<h2>In Summary</h2>\n<p><strong>Continuous Integration and Delivery</strong> can transform your development process by increasing the speed at which you release changes.</p>\n<p>Try adding up how much time you spend on deployments.\nI'm pretty sure you'll be surprised by the time savings potential of automating them.</p>\n<p>And the good part is you will typically set up your <strong>build and deployment pipelines</strong> once, and then continue benefiting from them for the lifetime of your project.</p>\n<p>If you want a step by step guide, I made a video showing <a href=\"https://youtu.be/QP0pi7xe24s\"><strong>how to implement a CI/CD pipeline from scratch.</strong></a>\nAnd the <strong>source code</strong> is also public, so you can add the <strong>GitHub Actions</strong> workflow file to your project.</p>\n<p>Thanks for reading.</p>\n<p>Hope that was helpful!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-build-ci-cd-pipeline-with-github-actions-and-dotnet",
            "title": "How To Build a CI/CD Pipeline With GitHub Actions And .NET",
            "summary": "Imagine being able to automatically build, test, and deploy your .NET applications with every code change. GitHub Actions are completely free and simple to use.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_039.png",
            "date_modified": "2023-05-27T00:00:00.000Z",
            "date_published": "2023-05-27T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core",
            "content_html": "<p>A multi-tenant application serves many customers from one deployment while keeping each tenant's data isolated.\nYou get there with a single database and logical isolation, or a database per tenant and physical isolation.\nIn EF Core, a global query filter on a <code>TenantId</code> column applies the tenant filter to every query for you.</p>\n<p>Most software applications today are built around the concept of <strong>multi-tenancy</strong>.</p>\n<p>One application serves multiple customers, while keeping their data <strong>isolated</strong>.</p>\n<p>You can approach <strong>multi-tenancy</strong> in two ways:</p>\n<ul>\n<li><strong>Single database</strong> and <strong>logical isolation</strong> of tenants</li>\n<li><strong>Multiple databases</strong> and <strong>physical isolation</strong> of tenants</li>\n</ul>\n<p>Which option you decide to use will depend mostly on your requirements.\nSome industries like healthcare require a high degree of <strong>data isolation</strong>, and using a <strong>database per tenant</strong> is a must.</p>\n<p>So how are we going to <strong>implement multi-tenancy</strong> support with <strong>EF Core</strong>?</p>\n<p>We can use <strong>Query Filters</strong> to apply a <strong>tenant filter</strong> to all database queries.</p>\n<p>Implement it once and you can almost forget about it.</p>\n<p>Let's see what are some of the problems we need to solve.</p>\n<h2>How To Use EF Core Query Filters</h2>\n<p>If you want an in-depth dive into <strong>Query filters</strong>, take a look at the newsletter issue where I talked about\n<a href=\"https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core\"><strong>using query filters with EF Core.</strong></a></p>\n<p>Here's a quick refresher on <strong>Query filters</strong>:</p>\n<ul>\n<li>Configure the <strong>query filter</strong> by calling <code>HasQueryFilter</code> for your entity</li>\n<li><strong>EF</strong> will apply it to all queries for that entity</li>\n<li>You can turn it off with <code>IgnoreQueryFilters</code></li>\n<li>Only <strong>one</strong> query filter <strong>per entity</strong> is allowed</li>\n</ul>\n<p>And here's a simple example:</p>\n<pre><code class=\"language-csharp\">modelBuilder\n   .Entity&lt;Order&gt;()\n   .HasQueryFilter(order =&gt; !order.IsDeleted);\n</code></pre>\n<p>All queries to the <code>Order</code> table will include an <code>IsDeleted = FALSE</code> condition.</p>\n<h2>Single Database Multi-Tenancy With EF Core</h2>\n<p>You will need two things to <strong>implement multi-tenancy</strong> on a <strong>single database</strong>:</p>\n<ul>\n<li>A way to know <strong>who</strong> the <strong>current tenant</strong> is</li>\n<li>A way to <strong>filter</strong> the <strong>data</strong> for that <strong>tenant</strong> only</li>\n</ul>\n<p>The typical approach for <strong>multi-tenancy</strong> on a <strong>single database</strong> is having a <code>TenantId</code> column in your tables.\nAnd then filtering on that column when querying the database.</p>\n<p>You can use the <strong>Query filters</strong> feature in <strong>EF Core</strong> to apply a <strong>global filter</strong> for some entity.</p>\n<p>Inside of the <code>OnModelCreating</code> method we configure the query filter on the <code>Order</code> entity:</p>\n<pre><code class=\"language-csharp\">public class OrdersDbContext : DbContext\n{\n    private readonly string _tenantId;\n\n    public OrdersDbContext(\n        DbContextOptions&lt;OrdersDbContext&gt; options,\n        TenantProvider tenantProvider)\n        : base(options)\n    {\n        _tenantId = tenantProvider.TenantId;\n    }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder\n            .Entity&lt;Order&gt;\n            .HasQueryFilter(o =&gt; o.TenantId == _tenantId);\n    }\n}\n</code></pre>\n<p>We're using the <code>TenantProvider</code> class to get the <strong>current tenant</strong> value.</p>\n<p>Here's what the <code>TenantProvider</code> implementation looks like:</p>\n<pre><code class=\"language-csharp\">public sealed class TenantProvider\n{\n    private const string TenantIdHeaderName = &quot;X-TenantId&quot;;\n\n    private readonly IHttpContextAccessor _httpContextAccessor;\n\n    public TenantProvider(IHttpContextAccessor httpContextAccessor)\n    {\n        _httpContextAccessor = httpContextAccessor;\n    }\n\n    public string TenantId =&gt; _httpContextAccessor\n        .HttpContext\n        .Request\n        .Headers[TenantIdHeaderName];\n}\n</code></pre>\n<p>The <code>TenantId</code> is coming from the HTTP request header in this example.</p>\n<p>A few other options to get the <code>TenantId</code> are:</p>\n<ul>\n<li>Query string - <code>api/orders?tenantId=example-tenant-id</code></li>\n<li>JWT Claim</li>\n<li>API Key</li>\n</ul>\n<p>If you want a more <strong>secure implementation</strong> you should go with <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT Claims</strong></a> or <a href=\"https://milanjovanovic.tech/blog/how-to-implement-api-key-authentication-in-aspnet-core\"><strong>API Keys</strong></a> to provide the <code>TenantId</code> value.</p>\n<h2>Separate Databases Multi-Tenancy With EF Core</h2>\n<p>What if we want to <strong>isolate</strong> each tenant to a <strong>separate database</strong>?</p>\n<p>Here are the changes we need to make:</p>\n<ul>\n<li>Applying different <strong>connection string per tenant</strong></li>\n<li>Resolving the connection string for each tenant <em>somehow</em></li>\n</ul>\n<p>You can't use <strong>Query filters</strong> here, since we are working with different databases.</p>\n<p>So you will need to store the <strong>tenant information</strong> and <strong>connection strings</strong> somewhere.</p>\n<p>A simple example would be store them in the <strong>application settings</strong>:</p>\n<pre><code class=\"language-json\">&quot;Tenants&quot;: {\n    { &quot;Id&quot;: &quot;tenant-1&quot;, &quot;ConnectionString&quot;: &quot;Host=tenant1.db;Database=tenant1&quot; },\n    { &quot;Id&quot;: &quot;tenant-2&quot;, &quot;ConnectionString&quot;: &quot;Host=tenant2.db;Database=tenant2&quot; }\n}\n</code></pre>\n<p>You can then register an <code>IOptions</code> instance with a list of <code>Tenant</code> objects.</p>\n<p>And we need to slightly modify the <code>TenantProvider</code> class to return a <strong>connection string</strong> for the current tenant:</p>\n<pre><code class=\"language-csharp\">public sealed class TenantProvider\n{\n    private const string TenantIdHeaderName = &quot;X-TenantId&quot;;\n\n    private readonly IHttpContextAccessor _httpContextAccessor;\n    private readonly TenantSettings _tenantSettings;\n\n    public TenantProvider(\n        IHttpContextAccessor httpContextAccessor,\n        IOptions&lt;TenantSettings&gt; tenantsOptions)\n    {\n        _httpContextAccessor = httpContextAccessor;\n        _tenants = tenantsOptions.Value;\n    }\n\n    public string TenantId =&gt; _httpContextAccessor\n        .HttpContext\n        .Request\n        .Headers[TenantIdHeaderName];\n\n    public string GetConnectionString()\n    {\n        return _tenantSettings.Tenants.Single(t =&gt; t.Id == TenantId);\n    }\n}\n</code></pre>\n<p>And the last part is registering your <code>DbContext</code> to <strong>dynamically resolve</strong> the <strong>connection string</strong> for the current <strong>tenant</strong>.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddDbContext&lt;OrdersDbContext&gt;((sp, o) =&gt;\n{\n    var tenantProvider = sp.GetRequiredService&lt;TenantProvider&gt;();\n\n    var connectionString = tenantProvider.GetConnectionString();\n\n    o.UseSqlServer(connectionString);\n});\n</code></pre>\n<p>On every request, we create a new <code>OrdersDbContext</code> and connect to the appropriate database for that tenant.</p>\n<p>You should definitely consider storing the tenant <strong>connection strings</strong> in a <strong>secure</strong> place like <strong>Azure Key Vault</strong>.</p>\n<h2>Closing Thoughts</h2>\n<p>I hope you now have a better understanding of how to build a <strong>multi-tenant system</strong> with <strong>EF Core</strong>.</p>\n<p>I showed you the bare bones implementation, which you can improve to make it more robust and secure.</p>\n<p>Building <strong>multi-tenant systems</strong> isn't easy, but when you understand the basic principles it shouldn't be too difficult either.</p>\n<p>That's all for this week.</p>\n<p>See you next Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core",
            "title": "Multi-Tenant Applications With EF Core",
            "summary": "One application serves multiple customers, while keeping their data isolated. You can approach multi-tenancy with a single database and logical isolation, or…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_038.png",
            "date_modified": "2023-05-20T00:00:00.000Z",
            "date_published": "2023-05-20T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/visualize-your-software-architecture-with-the-c4-model",
            "content_html": "<p>The C4 model is a lightweight way to describe software architecture, and C4 stands for context, containers, components, and code.\nIt gives you four abstractions (person, software system, container, component) and four diagram types that zoom in from the system as a whole down to the code.</p>\n<p><strong>Software architecture</strong> diagrams are a great way to communicate how you are planning to build a software system or how an existing software system works.</p>\n<p>However, the majority of software architecture <strong>diagrams</strong> I've seen are a total mess.</p>\n<p>If only there was a standard way to visualize your software architecture...</p>\n<p>Enter the <strong>C4 model</strong>, which stands for <strong>context</strong>, <strong>containers</strong>, <strong>components</strong>, and <strong>code</strong>.</p>\n<p>The <strong>C4 model</strong> is a lightweight approach to describing your <strong>software architecture</strong>.</p>\n<p>And it's essentially just two things, a predefined set of common <strong>abstractions</strong> and four diagram types:</p>\n<ul>\n<li><strong>Context diagram</strong> - high-level view of the system</li>\n<li><strong>Container diagram</strong> - shows the objects running inside a system</li>\n<li><strong>Component diagram</strong> - shows the building blocks that make each container</li>\n<li><strong>Code diagram</strong> - rarely used, typically UML or ER diagram</li>\n</ul>\n<p>Let's see how we can use the <strong>C4 model</strong> to visualize our <strong>software architecture</strong>.</p>\n<h2>First You Need Abstractions</h2>\n<p>Before we dive into the <strong>C4 diagrams</strong>, we first need to understand the ubiquitous language of the <strong>C4 model</strong>.</p>\n<blockquote>\n<p>A software system is made up of one or more containers (applications and data stores),\neach of which contains one or more components, which in turn are implemented by one or more code elements (classes, interfaces, objects, functions, etc).\nAnd people may use the software systems that we build.</p>\n</blockquote>\n<p>The <strong>C4 model</strong> defines a set of <strong>abstractions</strong> that you can use to describe your software systems:</p>\n<ul>\n<li>Person - the end user using your system</li>\n<li>Software system - highest level of abstraction that delivers value to end users</li>\n<li>Container - applications and data stores that make up a system</li>\n<li>Component - building blocks/modules that make up the container</li>\n</ul>\n<p>With these high-level concepts in place, let's take a look at how to use them in our <strong>C4 diagrams</strong>.</p>\n<h2>System Context Diagram</h2>\n<p>The <strong>System Context diagram</strong> is a high-level view of your software system.</p>\n<p>It shows your software system as the central part, and any external systems and users that your system interacts with.</p>\n<p>It should be <strong>technology agnostic</strong>, and the focus on the people and software systems instead of low-level details.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_037/system_context_diagram.jpg\" alt=\"C4 system context diagram showing a customer using an eShop connected to email and shipment services\">\n<p>The intended audience for the <strong>System Context Diagram</strong> is <em>everybody</em>.</p>\n<p>If you can show it to non-technical people and they are able to understand it, then you know you're on the right track.</p>\n<h2>Container Diagram</h2>\n<p>When you zoom into one software system, you get to the <strong>Container diagram</strong>.</p>\n<p>Your software system is comprised of multiple running parts - <strong>containers</strong>.</p>\n<p>A <strong>container</strong> can be a:</p>\n<ul>\n<li>Web application</li>\n<li>Single-page application</li>\n<li>Database</li>\n<li>File system</li>\n<li>Object store</li>\n<li>Message broker</li>\n</ul>\n<p>You can look at a <strong>container</strong> as a <strong>deployment unit</strong> that executes code or stores data.</p>\n<p>The <strong>Container diagram</strong> shows the high-level view of the software architecture and the <strong>major technology choices</strong>.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_037/container_diagram.jpg\" alt=\"C4 container diagram showing a React SPA calling a .NET REST API backed by a SQL database\">\n<p>The <strong>Container diagram</strong> is intended for technical people inside and outside of the software development team:</p>\n<ul>\n<li>Software architects</li>\n<li>Developers</li>\n<li>Operations/support staff</li>\n</ul>\n<h2>Component Diagram</h2>\n<p>Next you can zoom into an individual <strong>container</strong> to decompose it into its building blocks.</p>\n<p>The <strong>Component diagram</strong> show the individual <strong>components</strong> that make up a <strong>container</strong>:</p>\n<ul>\n<li>What each of the components are</li>\n<li>The technology and implementation details</li>\n</ul>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_037/component_diagram.jpg\" alt=\"C4 component diagram of ordering, payment, shipping, and storage components in a .NET REST API\">\n<p>The <strong>Component diagram</strong> is intended for software architects and developers.</p>\n<h2>Code Diagram</h2>\n<p>Finally, you can zoom into each <strong>component</strong> to show how it is implemented with <strong>code</strong>, typically using a UML class diagram or an ER diagram.</p>\n<p>This level is rarely used as it goes into too much technical detail for most use cases.</p>\n<p>However, there are supplementary diagrams that can be useful to fill in missing information by showcasing:</p>\n<ul>\n<li>Sequence of events</li>\n<li>Deployment information</li>\n<li>How systems interact at a higher level</li>\n</ul>\n<p>It's only recommended for the most <strong>important or complex components</strong>.</p>\n<p>Of course, the target audience are software architects and developers.</p>\n<h2>Takeaway</h2>\n<p>The <strong>C4 model</strong> is a tool for visual and verbal communication, enabling your team to talk in the same language and have a common understanding of the design.</p>\n<p>It's valuable for many reasons, to name a few:</p>\n<ul>\n<li>Product and business people benefit from understanding user behavior at the Context level</li>\n<li>Context and Container diagrams provide a simple view of how software systems work</li>\n<li>New developers can quickly understand system flows</li>\n</ul>\n<p>Now that you understand what the <strong>C4 model</strong> is, there's one question left to answer.</p>\n<p>Should you use the <strong>C4 model</strong> to express your <strong>software architecture</strong>?</p>\n<p>I can't give you a definitive answer.</p>\n<p>I used it with a lot of success.</p>\n<p>But I think you should at least give it a try.</p>\n<p>You can learn more about the <strong>C4 model</strong> <a href=\"https://c4model.com/\">here.</a></p>\n<p>Thank you for reading, and have an awesome Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/visualize-your-software-architecture-with-the-c4-model",
            "title": "Visualize Your Software Architecture With The C4 Model",
            "summary": "Most software architecture diagrams I've seen are a total mess. The C4 model is a lightweight, standard way to describe your system, with four diagram types…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_037.png",
            "date_modified": "2023-05-13T00:00:00.000Z",
            "date_published": "2023-05-13T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests",
            "content_html": "<p>You enforce software architecture by asserting its rules in an automated test.\nWith the <code>NetArchTest.Rules</code> library you load a set of types from an assembly, filter them, then apply a condition with <code>Should</code> or <code>ShouldNot</code>.\nThat catches a broken dependency direction or a naming convention violation without a manual code review.</p>\n<p><strong>Software architecture</strong> is a blueprint for how you should structure your system.\nYou can follow this blueprint strictly, or you can give yourself varying levels of freedom.</p>\n<p>But when deadlines are tight, and you start cutting corners, that beautiful software architecture you built crumbles like a house of cards.</p>\n<p>How can you <strong>enforce</strong> your <strong>software architecture</strong>?</p>\n<p>By writing <strong>architecture tests</strong>.</p>\n<p><strong>Architecture tests</strong> are automated tests that verify the structure and design of your code.</p>\n<p>You can use them to enforce your software architecture and the <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>direction of dependencies</strong></a> of your projects.</p>\n<p>In this week's issue I'll explain how to:</p>\n<ul>\n<li>Write architecture tests</li>\n<li>Enforce architecture</li>\n<li>Enforce design rules</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>Writing Architecture Tests</h2>\n<p>You write <strong>architecture tests</strong> the same as any unit test in your application.\nThere's an excellent library for writing architecture tests that already implements the boilerplate code we need to start writing tests.</p>\n<p>We're going to use the <code>NetArchTest.Rules</code> library for writing architecture tests.</p>\n<p>First, you have to install the NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package NetArchTest.Rules\n</code></pre>\n<p>And now you can use it to write rules in your test project.</p>\n<p>The starting point for writing architecture tests is the static <code>Types</code> class, which you can use to load a set of types.</p>\n<p>Once you have loaded your types you can further filter them to find a more specific set of types.</p>\n<p>Some of the available filtering methods:</p>\n<ul>\n<li><code>ResideInNamespace</code></li>\n<li><code>AreClasses</code></li>\n<li><code>AreInterfaces</code></li>\n<li><code>HaveNameStartingWith</code></li>\n<li><code>HaveNameEndingWith</code></li>\n</ul>\n<p>Finally, when you are satisfied with your selection, you can write the rule you want to enforce by calling <code>Should</code> or <code>ShouldNot</code>\nand applying the condition you want to check.</p>\n<p>Here's an example checking that all classes in the domain assembly are sealed:</p>\n<pre><code class=\"language-csharp\">var result = Types\n  .InAssembly(DomainAssembly)\n  .That()\n  .AreClasses()\n  .Should()\n  .BeSealed()\n  .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<h2>Enforcing Architecture Rules</h2>\n<p><strong>Architecture tests</strong> are particularly useful to <strong>enforce software architecture rules</strong> in a layered architecture or <a href=\"https://milanjovanovic.tech/blog/modular-monolith-architecture-dotnet\"><strong>Modular Monolith</strong></a>.</p>\n<p>Let's take the example of the <a href=\"https://milanjovanovic.tech/blog/clean-architecture-dotnet\"><strong>Clean architecture</strong></a>:</p>\n<ul>\n<li>Domain should not have any dependencies</li>\n<li>Application should not depend on Infrastructure</li>\n<li>Infrastructure should depend on Application and Domain</li>\n</ul>\n<p>Here's how you can write tests for enforcing architecture rules.</p>\n<p><strong>Domain should not have any dependencies</strong></p>\n<pre><code class=\"language-csharp\">var result = Types\n  .InAssembly(DomainAssembly)\n  .ShouldNot()\n  .HaveDependencyOnAny(&quot;Application&quot;, &quot;Infrastructure&quot;)\n  .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<p><strong>Application should not depend on Infrastructure</strong></p>\n<pre><code class=\"language-csharp\">var result = Types\n  .InAssembly(AplicationAssembly)\n  .Should()\n  .NotHaveDependencyOn(&quot;Infrastructure&quot;)\n  .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<p><strong>Infrastructure should depend on Application and Domain</strong></p>\n<p>How the <code>NetArchTest.Rules</code> library works is by scanning the imported namespaces of your types.</p>\n<p>Because of this, writing negative conditions like in the previous two examples is straightforward.</p>\n<p>But writing positive conditions has to be scoped to a more specific set of types.</p>\n<p>For example, we can validate this dependency by checking that all repositories must have a dependency on the <code>Domain</code> namespace.</p>\n<pre><code class=\"language-csharp\">var result = Types\n  .InAssembly(InfrastructureAssembly)\n  .HaveNameEndingWith(&quot;Repository&quot;)\n  .Should()\n  .HaveDependencyOn(&quot;Domain&quot;)\n  .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<h2>Enforcing Design Rules</h2>\n<p>Another valuable <strong>use case</strong> for <strong>architecture tests</strong> is <strong>enforcing design rules</strong> in your application.</p>\n<p>Design rules are more specific than project references, and focus on the implementation details of your classes.</p>\n<p>Here are some <strong>design rules</strong> that you can enforce:</p>\n<ul>\n<li>Services must be internal</li>\n<li>Entities and Value objects must be sealed</li>\n<li>Controllers can't depend on repositories directly</li>\n<li>Command (or query) handlers must follow a naming convention</li>\n</ul>\n<p>The possibilities are endless, and it's up to you how many design rules you want to enforce.</p>\n<p>Here's how you can write tests for enforcing design rules.</p>\n<p><strong>Command handlers must end with <code>CommandHandler</code></strong></p>\n<pre><code class=\"language-csharp\">var result = Types\n    .InAssembly(ApplicationAssembly)\n    .That()\n    .ImplementInterface(typeof(ICommandHandler))\n    .Should()\n    .HaveNameEndingWith(&quot;CommandHandler&quot;)\n    .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<p><strong>Controllers can't directly reference repositories</strong></p>\n<pre><code class=\"language-csharp\">var result = Types\n    .InAssembly(ApiAssembly)\n    .That()\n    .HaveNameEndingWith(&quot;Controller&quot;)\n    .ShouldNot()\n    .HaveDependencyOn(&quot;Infrastructure.Repositories&quot;)\n    .GetResult();\n\nAssert.True(result.IsSuccessful);\n</code></pre>\n<h2>Takeaway</h2>\n<p><strong>Architecture tests</strong> are an easy way to <strong>enforce software architecture</strong> and design rules with automated tests.</p>\n<p>One of the best investments you can make as a software engineer is writing automated tests.\nYou write the tests once, and use them to verify your system forever.\nGranted, you also have to maintain the tests over time as your system grows.</p>\n<p>Manually enforcing software architecture with pair programming and constant PR reviews is:</p>\n<ul>\n<li>Error prone</li>\n<li>Time consuming</li>\n<li>Not cost effective</li>\n</ul>\n<p><strong>Architecture tests</strong> really shine here, since you can write them quickly and reduce the cost of enforcing your software architecture rules to zero.</p>\n<p>Thanks for reading.</p>\n<p>Hope that was helpful!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests",
            "title": "Enforcing Software Architecture With Architecture Tests",
            "summary": "When deadlines are tight and you start cutting corners, that beautiful software architecture you built crumbles like a house of cards. So how do you enforce it?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_036.png",
            "date_modified": "2023-05-06T00:00:00.000Z",
            "date_published": "2023-05-06T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/health-checks-in-asp-net-core",
            "content_html": "<p>ASP.NET Core has built-in support for health checks.\nRegister the services with <code>AddHealthChecks</code> and expose an endpoint with <code>MapHealthChecks(&quot;/health&quot;)</code>, and each check reports <code>Healthy</code>, <code>Degraded</code>, or <code>Unhealthy</code>.\nYou can write a custom check by implementing <code>IHealthCheck</code>, or install a ready-made package from the <code>AspNetCore.Diagnostics.HealthChecks</code> repository.</p>\n<p>We all want to build <strong>robust</strong> and <strong>reliable</strong> applications that can scale indefinitely and handle any number of requests.</p>\n<p>But with <strong>distributed systems</strong> and <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>microservices architectures</strong></a> growing in complexity, it's becoming increasingly harder to <strong>monitor</strong> the <strong>health</strong> of our applications.</p>\n<p>It's vital that you have a system in place to receive quick feedback of your application <strong>health.</strong></p>\n<p>That's where <strong>health checks</strong> come in.</p>\n<p><strong>Health checks</strong> provide a way to monitor and verify the health of various components of an application including:</p>\n<ul>\n<li>Databases</li>\n<li>APIs</li>\n<li>Caches</li>\n<li>External services</li>\n</ul>\n<p>Here's what I'll show you in this week's newsletter:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core#what-are-health-checks\">What are health checks</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core#adding-custom-health-checks\">Adding a custom health check</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core#using-existing-health-check-libraries\">Using existing health check libraries</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/health-checks-in-asp-net-core#formatting-health-checks-response\">Customizing the health checks response format</a></li>\n</ul>\n<p>Let's see how to implement <strong>health checks</strong> in <strong>ASP.NET Core</strong>.</p>\n<h2>What Are Health Checks?</h2>\n<p><strong>Health checks</strong> are a proactive mechanism for monitoring and verifying the <strong>health</strong> and <strong>availability</strong> of an application in <strong>ASP.NET Core.</strong></p>\n<p>ASP.NET Core has <strong>built-in support</strong> for implementing <strong>health checks.</strong></p>\n<p>Here's the basic configuration, which registers the health check services and adds the <code>HealthCheckMiddleware</code> to respond at the specified URL.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddHealthChecks();\n\nvar app = builder.Build();\n\napp.MapHealthChecks(&quot;/health&quot;);\n\napp.Run();\n</code></pre>\n<p>The health check returns a <code>HealthStatus</code> value indicating the health of the service.</p>\n<p>There are three distinct <code>HealthStatus</code> values:</p>\n<ul>\n<li><code>HealthStatus.Healthy</code></li>\n<li><code>HealthStatus.Degraded</code></li>\n<li><code>HealthStatus.Unhealthy</code></li>\n</ul>\n<p>You can use the <code>HealthStatus</code> to indicate the different states of your application.</p>\n<p>For example, if the application is functioning slower than expected you can return <code>HealthStatus.Degraded</code>.</p>\n<h2>Adding Custom Health Checks</h2>\n<p>You can create <strong>custom health checks</strong> by implementing the <code>IHealthCheck</code> interface.</p>\n<p>For example, you can implement a check to see if your <strong>SQL</strong> database is available.</p>\n<p>It's important to use a query that can complete quickly in the database, like <code>SELECT 1</code>.</p>\n<p>Here's a <strong>custom health check</strong> implementation example in the <code>SqlHealthCheck</code> class:</p>\n<pre><code class=\"language-csharp\">public class SqlHealthCheck : IHealthCheck\n{\n    private readonly string _connectionString;\n\n    public SqlHealthCheck(IConfiguration configuration)\n    {\n        _connectionString = configuration.GetConnectionString(&quot;Database&quot;);\n    }\n\n    public async Task&lt;HealthCheckResult&gt; CheckHealthAsync(\n        HealthCheckContext context,\n        CancellationToken cancellationToken = default)\n    {\n        try\n        {\n            using var sqlConnection = new SqlConnection(_connectionString);\n\n            await sqlConnection.OpenAsync(cancellationToken);\n\n            using var command = sqlConnection.CreateCommand();\n            command.CommandText = &quot;SELECT 1&quot;;\n\n            await command.ExecuteScalarAsync(cancellationToken);\n\n            return HealthCheckResult.Healthy();\n        }\n        catch(Exception ex)\n        {\n            return HealthCheckResult.Unhealthy(\n                context.Registration.FailureStatus,\n                exception: ex);\n        }\n    }\n}\n</code></pre>\n<p>After you implement the <strong>custom health check</strong>, you need to register it.</p>\n<p>The previous call to <code>AddHealthChecks</code> now becomes:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHealthChecks()\n    .AddCheck&lt;SqlHealthCheck&gt;(&quot;custom-sql&quot;, HealthStatus.Unhealthy);\n</code></pre>\n<p>We're giving it a custom name and setting which status to use as the failure result in <code>HealthCheckContext.Registration.FailureStatus</code>.</p>\n<p>But stop and think for a moment.</p>\n<p>Do you want to implement a <strong>custom health check</strong> on your own for <strong>every external service</strong> that you have?</p>\n<p>Of course not! There's a better solution.</p>\n<h2>Using Existing Health Check Libraries</h2>\n<p>Before you start implementing a custom <strong>health check</strong> for everything, you should first see if there's already an <strong>existing library.</strong></p>\n<p>In the <a href=\"https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks\"><code>AspNetCore.Diagnostics.HealthChecks</code></a> repository you can find\na wide collection <strong>health check</strong> packages for frequently used services and libraries.</p>\n<p>Here are just a few examples:</p>\n<ul>\n<li>SQL Server - <code>AspNetCore.HealthChecks.SqlServer</code></li>\n<li>Postgres - <code>AspNetCore.HealthChecks.Npgsql</code></li>\n<li>Redis - <code>AspNetCore.HealthChecks.Redis</code></li>\n<li>RabbitMQ - <code>AspNetCore.HealthChecks.RabbitMQ</code></li>\n<li>AWS S3 - <code>AspNetCore.HealthChecks.Aws.S3</code></li>\n<li>SignalR - <code>AspNetCore.HealthChecks.SignalR</code></li>\n</ul>\n<p>Here's how to add health checks for <strong>PostgreSQL</strong> and <strong>RabbitMQ</strong>:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHealthChecks()\n    .AddCheck&lt;SqlHealthCheck&gt;(&quot;custom-sql&quot;, HealthStatus.Unhealthy);\n    .AddNpgSql(pgConnectionString)\n    .AddRabbitMQ(rabbitConnectionString)\n</code></pre>\n<h2>Formatting Health Checks Response</h2>\n<p>By default, the endpoint returning you <strong>health check</strong> status will return a string value representing a <code>HealthStatus</code>.</p>\n<p>This isn't practical if you have <strong>multiple health checks</strong> configured, as you'd want to view the health status individually per service.</p>\n<p>To make matters worse, if one of the services is failing the entire response will return <code>Unhealthy</code> and you don't know what's causing the issue.</p>\n<p>You can solve this by providing a <code>ResponsWriter</code>, and there's an existing one in the <code>AspNetCore.HealthChecks.UI.Client</code> library.</p>\n<p>Let's install the <strong>NuGet</strong> package:</p>\n<pre><code class=\"language-powershell\">Install-Package AspNetCore.HealthChecks.UI.Client\n</code></pre>\n<p>And you need to slightly update the call to <code>MapHealthChecks</code> to use the <code>ResponseWriter</code> coming from this library:</p>\n<pre><code class=\"language-csharp\">app.MapHealthChecks(\n    &quot;/health&quot;,\n    new HealthCheckOptions\n    {\n        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n    });\n</code></pre>\n<p>After making these changes, here's what the response from the health check endpoint looks like:</p>\n<pre><code class=\"language-json\">{\n  &quot;status&quot;: &quot;Unhealthy&quot;,\n  &quot;totalDuration&quot;: &quot;00:00:00.3285211&quot;,\n  &quot;entries&quot;: {\n    &quot;npgsql&quot;: {\n      &quot;data&quot;: {},\n      &quot;duration&quot;: &quot;00:00:00.1183517&quot;,\n      &quot;status&quot;: &quot;Healthy&quot;,\n      &quot;tags&quot;: []\n    },\n    &quot;rabbitmq&quot;: {\n      &quot;data&quot;: {},\n      &quot;duration&quot;: &quot;00:00:00.1189561&quot;,\n      &quot;status&quot;: &quot;Healthy&quot;,\n      &quot;tags&quot;: []\n    },\n    &quot;custom-sql&quot;: {\n      &quot;data&quot;: {},\n      &quot;description&quot;: &quot;Unable to connect to the database.&quot;,\n      &quot;duration&quot;: &quot;00:00:00.2431813&quot;,\n      &quot;exception&quot;: &quot;Unable to connect to the database.&quot;,\n      &quot;status&quot;: &quot;Unhealthy&quot;,\n      &quot;tags&quot;: []\n    }\n  }\n}\n</code></pre>\n<h2>Takeaway</h2>\n<p>Application monitoring is important to track availability, resource usage, and changes to performance in your application.</p>\n<p>I've used <strong>health checks</strong> before to implement <strong>failover scenarios</strong> in a <strong>cloud deployment</strong>.\nWhen one application instance stops responding with a healthy result, a new one is created to continue serving requests.</p>\n<p>It's easy to monitor the health of your ASP.NET Core applications by <strong>exposing health checks</strong> for your services.</p>\n<p>You can decide to implement <strong>custom health checks</strong>, but first consider if there are <strong>existing solutions</strong>.</p>\n<p>Thank you for reading, and have an awesome Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/health-checks-in-asp-net-core",
            "title": "Health Checks In ASP.NET Core For Monitoring Your Applications",
            "summary": "With distributed systems growing in complexity, it's getting harder to monitor the health of our applications. That's where health checks come in.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_035.png",
            "date_modified": "2023-04-29T00:00:00.000Z",
            "date_published": "2023-04-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages",
            "content_html": "<p>The <strong>Idempotent Consumer</strong> pattern prevents a message from being processed more than once.\nWhen a message arrives, the consumer checks if its unique identifier was already processed.\nIf it was, the duplicate is ignored; otherwise, the consumer handles the message and stores the identifier in a database table.</p>\n<p>What happens when a <strong>message is retried</strong> in an <strong>event-driven system</strong>?</p>\n<p>It happens more often than you think.</p>\n<p>The <strong>worst case scenario</strong> is that the <strong>message is processed twice</strong>, and the <strong>side effects</strong> can also be applied more than once.</p>\n<p>Do you want your bank account to be double charged?</p>\n<p>I'll assume the answer is no, of course.</p>\n<p>You can use the <strong>Idempotent Consumer</strong> pattern to solve this problem.</p>\n<p>In this week's issue I will show you:</p>\n<ul>\n<li>How the Idempotent Consumer pattern works</li>\n<li>How to implement an Idempotent Consumer</li>\n<li>The tradeoffs you need to consider</li>\n</ul>\n<p>Let's see why the <strong>Idempotent Consumer</strong> pattern is valuable.</p>\n<h2>How The Idempotent Consumer Pattern Works</h2>\n<p>What's the idea behind the <strong>Idempotent Consumer pattern</strong>?</p>\n<blockquote>\n<p>An idempotent operation is one that has no additional effect if it is called more than once with the same input parameters.</p>\n</blockquote>\n<p>We want to avoid handling the same message more than once.</p>\n<p>This would require <strong>Exactly-once</strong> message delivery guarantees from our messaging system.\nAnd this is a really hard problem to solve in distributed systems.</p>\n<p>A looser delivery guarantee is <strong>At-least-once</strong>, where we are aware that retries can happen and we can receive the same message more than once.</p>\n<p>The <strong>Idempotent Consumer</strong> pattern works well with <strong>At-least-once</strong> message delivery, and solves the <strong>problem of duplicate messages.</strong></p>\n<p>Here's what the algorithm looks like from the moment we receive a message:</p>\n<ol>\n<li>Was the message already processed?</li>\n<li>If yes, it's a duplicate and there's nothing to do</li>\n<li>If not, we need to handle the message</li>\n<li>We also need to store the message identifier</li>\n</ol>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_034/idempotent_consumer_algorithm.png\" alt=\"Idempotent consumer flow checking for a processed message before handling it and storing its ID\">\n<p>We need a <strong>unique identifier</strong> for every <strong>message</strong> we receive, and a table in the database to store processed messages.</p>\n<p>However, it's interesting how we choose the implement the message handling and storing of the processed message identifier.</p>\n<p>You can implement the idempotent consumer as a decorator around a regular message handler.</p>\n<p>I'll show you two implementations:</p>\n<ul>\n<li>Lazy idempotent consumer</li>\n<li>Eager idempotent consumer</li>\n</ul>\n<h2>Lazy Idempotent Consumer</h2>\n<p>The <strong>lazy idempotent consumer</strong> matches the flow shown in the algorithm above.</p>\n<p>Lazy refers to how we store the message identifer to mark it as processed.</p>\n<p>In the happy path, we handle the message and store the message identifier.</p>\n<p>If the message handler throws an exception, we never store the message identifier and the consumer can be executed again.</p>\n<p>Here's what the implementation looks like:</p>\n<pre><code class=\"language-csharp\">public class IdempotentConsumer&lt;T&gt; : IHandleMessages&lt;T&gt;\n    where T : IMessage\n{\n    private readonly IMessageRepository _messageRepository;\n    private readonly IHandleMessages&lt;T&gt; _decorated;\n\n    public IdempotentConsumer(\n        IMessageRepository messageRepository,\n        IHandleMessages&lt;T&gt; decorated)\n    {\n        _messageRepository = messageRepository;\n        _decorated = decorated;\n    }\n\n    public async Task Handle(T message)\n    {\n        if (_messageRepository.IsProcessed(message.Id))\n        {\n            return;\n        }\n\n        await _decorated.Handle(message);\n\n        _messageRepository.Store(message.Id);\n    }\n}\n</code></pre>\n<h2>Eager Idempotent Consumer</h2>\n<p>The <strong>eager idempotent consumer</strong> is slightly different from the lazy implementation, but the end result is the same.</p>\n<p>In this version, we eagerly store the message identifier in the database and then continue to handle the message.</p>\n<p>If the handler throws an exception, we need to perform cleanup in the database and remove the eagerly stored message identifier.</p>\n<p>Otherwise, we risk leaving the system in an inconsistent state since the message was never handled correctly.</p>\n<p>Here's what the implementation looks like:</p>\n<pre><code class=\"language-csharp\">public class IdempotentConsumer&lt;T&gt; : IHandleMessages&lt;T&gt;\n    where T : IMessage\n{\n    private readonly IMessageRepository _messageRepository;\n    private readonly IHandleMessages&lt;T&gt; _decorated;\n\n    public IdempotentConsumer(\n        IMessageRepository messageRepository,\n        IHandleMessages&lt;T&gt; decorated)\n    {\n        _messageRepository = messageRepository;\n        _decorated = decorated;\n    }\n\n    public async Task Handle(T message)\n    {\n        try\n        {\n            if (_messageRepository.IsProcessed(message.Id))\n            {\n                return;\n            }\n\n            _messageRepository.Store(message.Id);\n\n            await _decorated.Handle(message);\n        }\n        catch (Exception e)\n        {\n            _messageRepository.Remove(message.Id);\n\n            throw;\n        }\n    }\n}\n</code></pre>\n<h2>In Summary</h2>\n<p><strong>Idempotency</strong> is an interesting problem to solve in a software system.</p>\n<p>Some operations are <strong>naturally idempotent</strong>, and we don't need the overhead of the <strong>Idempotent Consumer</strong> pattern.</p>\n<p>However, for those operations that aren't naturally idempotent, the <strong>Idempotent Consumer</strong> is a great solution.</p>\n<p>The high-level algorithm is simple, and you can take two approaches in the implementation:</p>\n<ul>\n<li>Lazy storing of message identifiers</li>\n<li>Eager storing of message identifiers</li>\n</ul>\n<p>I prefer to use the <strong>lazy approach</strong>, and only <strong>store the message identifier</strong> in the database when the <strong>handler completes successfully.</strong></p>\n<p>It's easier to reason about and there is one less call to the database.</p>\n<p>Thanks for reading.</p>\n<p>Hope that was helpful.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/idempotent-consumer-handling-duplicate-messages",
            "title": "Idempotent Consumer - Handling Duplicate Messages",
            "summary": "What happens when a message is retried in an event-driven system? The worst case scenario is that the message is processed twice, and the side effects can be…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_034.png",
            "date_modified": "2023-04-22T00:00:00.000Z",
            "date_published": "2023-04-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/ef-core-raw-sql-queries",
            "content_html": "<p>EF Core supports raw SQL queries.\nEF7 added support for queries returning scalar types, and EF8 added the <code>SqlQuery</code> and <code>SqlQueryRaw</code> methods for querying unmapped types that are not part of the EF model.\n<code>SqlQuery</code> parameterizes interpolated values, which protects against SQL injection.</p>\n<p><strong>EF Core</strong> is getting many new and exciting features in the upcoming version.</p>\n<p><strong>EF7</strong> introduced support for returning <strong>scalar types</strong> using <strong>SQL</strong> queries.</p>\n<p>And now we're getting support for <strong>querying unmapped types</strong> with <strong>raw SQL queries</strong> in <strong>EF8.</strong></p>\n<p>This is exactly what <a href=\"https://milanjovanovic.tech/blog/ef-core-vs-dapper\"><strong>Dapper</strong></a> offers out of the box, and it's good to see <strong>EF Core</strong> catching up.</p>\n<p>In this week's newsletter, I'm going to cover how to use <strong>EF Core</strong> for:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries#ef-core-and-sql-queries\">Raw SQL queries</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries#composing-sql-queries-with-linq\">Composing SQL queries with LINQ</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/ef-core-raw-sql-queries#sql-queries-for-data-modifications\">Executing data modifications with SQL</a></li>\n</ul>\n<p>Let's dive in!</p>\n<h2>EF Core And SQL Queries</h2>\n<p><strong>EF7</strong> added support for <strong>raw SQL queries</strong> returning scalar types.\n<strong>EF8</strong> is taking this a step further with raw SQL queries that can return any mappable type, without having to include it in the <strong>EF model</strong>.</p>\n<p>You can query unmapped types with the <code>SqlQuery</code> and <code>SqlQueryRaw</code> methods.</p>\n<p>The <code>SqlQuery</code> method uses string interpolation to parameterize the query, protecting against <strong>SQL injection</strong> attacks.</p>\n<p>Here's an example query returning an <code>OrderSummary</code> list:</p>\n<pre><code class=\"language-csharp\">var startDate = new DateOnly(2023, 1, 1);\n\nvar ordersIn2023 = await dbContext\n    .Database\n    .SqlQuery&lt;OrderSummary&gt;(\n        $&quot;SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn &gt;= {startDate}&quot;)\n    .ToListAsync();\n</code></pre>\n<p>This will be the <strong>SQL</strong> sent to the database:</p>\n<pre><code class=\"language-sql\">SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn &gt;= @p0\n</code></pre>\n<p>The type used for the query result can have a parameterized constructor.\nThe property names don't need to match the column names in the database, but they do have to match the names of the values in the result set.</p>\n<p>You can also execute raw SQL queries and return results with:</p>\n<ul>\n<li>Views</li>\n<li>Functions</li>\n<li>Stored procedures</li>\n</ul>\n<h2>Composing SQL Queries With LINQ</h2>\n<p>An interesting thing about <code>SqlQuery</code> is that it returns <code>IQueryable</code>, which can be further composed with <strong>LINQ.</strong></p>\n<p>You can add a <code>Where</code> statement after calling <code>SqlQuery</code>:</p>\n<pre><code class=\"language-csharp\">var startDate = new DateOnly(2023, 1, 1);\n\nvar ordersIn2023 = await dbContext\n    .Database\n    .SqlQuery&lt;OrderSummary&gt;(&quot;SELECT * FROM OrderSummaries AS o&quot;)\n    .Where(o =&gt; o.CreatedOn &gt;= startDate)\n    .ToListAsync();\n</code></pre>\n<p>However, the generated <strong>SQL</strong> isn't optimal:</p>\n<pre><code class=\"language-sql\">SELECT s.Id, s.CustomerId, s.TotalPrice, s.CreatedOn\nFROM (\n    SELECT * FROM OrderSummaries AS o\n) AS s\nWHERE s.CreatedOn &gt;= @p0\n</code></pre>\n<p>Another possibility is to combine an <code>OrderBy</code> statement with <code>Skip</code> and <code>Take</code>:</p>\n<pre><code class=\"language-csharp\">var startDate = new DateOnly(2023, 1, 1);\n\nvar ordersIn2023 = await dbContext\n    .Database\n    .SqlQuery&lt;OrderSummary&gt;(\n        $&quot;SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn &gt;= {startDate}&quot;)\n    .OrderBy(o =&gt; o.Id)\n    .Skip(10)\n    .Take(5)\n    .ToListAsync();\n</code></pre>\n<p>This would be the generated <strong>SQL</strong> for the previous query:</p>\n<pre><code class=\"language-sql\">SELECT s.Id, s.CustomerId, s.TotalPrice, s.CreatedOn\nFROM (\n    SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn &gt;= @p0\n) AS s\nORDER BY s.Id\nOFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY\n</code></pre>\n<p>In case you're wondering, the performance is similar to <strong>LINQ</strong> queries using <code>Select</code> projections.</p>\n<p>I ran some benchmarks, and didn't notice any significant performance improvement.</p>\n<p>This feature will be very useful if you're more comfortable with writing <strong>SQL</strong> or you want to fetch data from views, functions, and stored procedures.</p>\n<h2>SQL Queries For Data Modifications</h2>\n<p>If you want to modify data in the database with <strong>SQL</strong>, you will typically write a query that doesn't return a result.</p>\n<p>The SQL query can be an <code>UPDATE</code> or <code>DELETE</code> statement, or even a stored procedure call.</p>\n<p>You can use the <code>ExecuteSql</code> method to execute this type of query with <strong>EF Core</strong>:</p>\n<pre><code class=\"language-csharp\">var startDate = new DateOnly(2023, 1, 1);\n\ndbContext.Database.ExecuteSql(\n    $&quot;UPDATE Orders SET Status = 5 WHERE CreatedOn &gt;= {startDate}&quot;);\n</code></pre>\n<p><code>ExecuteSql</code> also protects from SQL injection by parameterizing arguments, just like <code>SqlQuery</code>.</p>\n<p>With <strong>EF7</strong> you can write the above query with <strong>LINQ</strong> and the <code>ExecuteUpdate</code> method.\nThere's also the <code>ExecuteDelete</code> method for deleting records.</p>\n<h2>In Summary</h2>\n<p><strong>EF7</strong> introduced support for raw SQL queries returning <strong>scalar</strong> values.</p>\n<p><strong>EF8</strong> will add support for <strong>raw SQL queries</strong> returning <strong>unmapped types</strong> with <code>SqlQuery</code> and <code>SqlQueryRaw</code>.</p>\n<p>I like the direction that <strong>EF</strong> is going, introducing more flexibility for querying the database.</p>\n<p>The performance isn't as good as <strong>Dapper</strong>, unfortunately.\nBut it's close enough that network costs will play the bigger factor.</p>\n<p>I will probably be using only <strong>EF</strong> moving forward since it covers more use cases.</p>\n<p>Thank you for reading, and have an awesome Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/ef-core-raw-sql-queries",
            "title": "EF Core Raw SQL Queries",
            "summary": "EF7 introduced support for returning scalar types using SQL queries. And now we're getting support for querying unmapped types with raw SQL queries in EF8.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_033.png",
            "date_modified": "2023-04-15T00:00:00.000Z",
            "date_published": "2023-04-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core",
            "content_html": "<p>ASP.NET Core 7 introduced built-in rate limiting middleware in the <code>Microsoft.AspNetCore.RateLimiting</code> namespace.\nYou register policies with <code>AddRateLimiter</code>, apply the middleware with <code>app.UseRateLimiter</code>, and attach a policy using the <code>EnableRateLimiting</code> attribute on controllers or <code>RequireRateLimiting</code> on Minimal API endpoints.</p>\n<p>Rate limiting is a technique to limit the number of requests to a server or an API.</p>\n<p>A limit is introduced within a given time period to prevent server overload and protect against abuse.</p>\n<p>In ASP.NET Core 7, we have a built-in rate limiter middleware that's easy to integrate into your API.</p>\n<p>We're going to cover four rate limiter algorithms:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core#fixed-window-limiter\">Fixed window</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core#sliding-window-limiter\">Sliding window</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core#token-bucket-limiter\">Token bucket</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core#concurrency-limiter\">Concurrency</a></li>\n</ul>\n<p>Let's see how we can work with rate limiting.</p>\n<h2>What Is Rate Limiting?</h2>\n<p>Rate limiting is about restricting the number of requests to an API, usually within a specific time window or based on other criteria.</p>\n<p>This is practical for a few reasons:</p>\n<ul>\n<li>Prevents overloading of servers or applications</li>\n<li>Improves security and guards against DDoS attacks</li>\n<li>Reduces costs by preventing unnecessary resource usage</li>\n</ul>\n<p>In a multi-tenant application, each unique user can have a limitation on the number of API requests.</p>\n<h2>Configuring Rate Limiting</h2>\n<p>ASP.NET Core 7 introduced built-in rate limiting middleware in the <code>Microsoft.AspNetCore.RateLimiting</code> namespace.</p>\n<p>To add rate limiting to your application, you first need to register the rate limiting services:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(options =&gt;\n{\n    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;\n\n    // We'll talk about adding specific rate limiting policies later.\n});\n</code></pre>\n<p>I suggest updating the <code>RejectionStatusCode</code> to <code>429 (Too Many Requests)</code> because it's more correct.\nThe default value is <code>503 (Service Unavailable)</code>.</p>\n<p>And you also have to apply the <code>RateLimitingMiddleware</code>:</p>\n<pre><code class=\"language-csharp\">app.UseRateLimiter();\n</code></pre>\n<p>That's everything you'll need.</p>\n<p>Let's see the rate limiting algorithms we can use.</p>\n<h2>Fixed Window Limiter</h2>\n<p>The <code>AddFixedWindowLimiter</code> method configures a fixed window limiter.</p>\n<p>The <code>Window</code> value determines the time window.</p>\n<p>When a time window expires, a new one starts, and the request limit is reset.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    rateLimiterOptions.AddFixedWindowLimiter(&quot;fixed&quot;, options =&gt;\n    {\n        options.PermitLimit = 10;\n        options.Window = TimeSpan.FromSeconds(10);\n        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n        options.QueueLimit = 5;\n    });\n});\n</code></pre>\n<h2>Sliding Window Limiter</h2>\n<p>The sliding window algorithm is similar to the fixed window, but it introduces segments in a window.</p>\n<p>Here's how it works:</p>\n<ul>\n<li>Each time window is divided into multiple segments</li>\n<li>The window slides one segment each segment interval</li>\n<li>The segment interval is (window_time)/(segments_per_window)</li>\n<li>When a segment expires, the requests taken in that segment are added to the current segment</li>\n</ul>\n<p>The <code>AddSlidingWindowLimiter</code> method configures a sliding window limiter.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    rateLimiterOptions.AddSlidingWindowLimiter(&quot;sliding&quot;, options =&gt;\n    {\n        options.PermitLimit = 10;\n        options.Window = TimeSpan.FromSeconds(10);\n        options.SegmentsPerWindow = 2;\n        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n        options.QueueLimit = 5;\n    });\n});\n</code></pre>\n<h2>Token Bucket Limiter</h2>\n<p>The token bucket algorithm is similar to the sliding window, but instead of adding back the requests from the expired segment,\na fixed number of tokens are added after each replenishment period.</p>\n<p>The total number of tokens can never exceed the token limit.</p>\n<p>The <code>AddTokenBucketLimiter</code> method configures a token bucket limiter.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    rateLimiterOptions.AddTokenBucketLimiter(&quot;token&quot;, options =&gt;\n    {\n        options.TokenLimit = 100;\n        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n        options.QueueLimit = 5;\n        options.ReplenishmentPeriod = TimeSpan.FromSeconds(10);\n        options.TokensPerPeriod = 20;\n        options.AutoReplenishment = true;\n    });\n});\n</code></pre>\n<p>When <code>AutoReplenishment</code> is <code>true</code>, an internal timer will execute every <code>ReplenishmentPeriod</code> and replenish the tokens.</p>\n<h2>Concurrency Limiter</h2>\n<p>The concurrency limiter is the most straightforward algorithm, and it just limits the number of concurrent requests.</p>\n<p>The <code>AddConcurrencyLimiter</code> method configures a concurrency limiter.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddRateLimiter(rateLimiterOptions =&gt;\n{\n    rateLimiterOptions.AddConcurrencyLimiter(&quot;concurrency&quot;, options =&gt;\n    {\n        options.PermitLimit = 10;\n        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n        options.QueueLimit = 5;\n    });\n});\n</code></pre>\n<p>There's no time component involved in this case. The only parameter is the number of concurrent requests.</p>\n<h2>Using Rate Limiting In Your API</h2>\n<p>Now that we have configured our rate limiting policies, let's see how we can use them in our API.</p>\n<p>There are slight differences between controllers and minimal API endpoints, so I'll cover them in separate examples.</p>\n<p><strong>Controllers</strong></p>\n<p>To add rate limiting on a controller we use the <code>EnableRateLimiting</code> and <code>DisableRateLimiting</code> attributes.</p>\n<p><code>EnableRateLimiting</code> can be applied on the controller or on the individual endpoints.</p>\n<pre><code class=\"language-csharp\">[EnableRateLimiting(&quot;fixed&quot;)]\npublic class TransactionsController\n{\n    private readonly ISender _sender;\n\n    public TransactionsController(ISender sender)\n    {\n        _sender = sender;\n    }\n\n    [EnableRateLimiting(&quot;sliding&quot;)]\n    public async Task&lt;IActionResult&gt; GetTransactions()\n    {\n        return Ok(await _sender.Send(new GetTransactionsQuery()));\n    }\n\n    [DisableRateLimiting]\n    public async Task&lt;IActionResult&gt; GetTransactionById(int id)\n    {\n        return Ok(await _sender.Send(new GetTransactionByIdQuery(id)));\n    }\n}\n</code></pre>\n<p>In the previous example:</p>\n<ul>\n<li>All endpoints in the <code>TransactionsController</code> will use a <strong>fixed window</strong> policy</li>\n<li>The <code>GetTransactions</code> endpoint will use a <strong>sliding window</strong> policy</li>\n<li>The <code>GetTransactionById</code> endpoint won't have any rate limiting applied</li>\n</ul>\n<p><strong>Minimal APIs</strong></p>\n<p>In a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint you can configure the rate limit policy by calling <code>RequireRateLimiting</code> and specifying the policy name.</p>\n<p>We're using the <strong>token bucket</strong> policy in this example.</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/transactions&quot;, async (ISender sender) =&gt;\n{\n    return Results.Ok(await sender.Send(new GetTransactionsQuery()));\n})\n.RequireRateLimiting(&quot;token&quot;);\n</code></pre>\n<h2>Closing Thoughts</h2>\n<p>It's great that we can quickly introduce <strong>rate limiting</strong> in ASP.NET Core.</p>\n<p>You can choose from one of the existing rate limiter algorithms:</p>\n<ul>\n<li>Fixed window</li>\n<li>Sliding window</li>\n<li>Token bucket</li>\n<li>Concurrency</li>\n</ul>\n<p>Here are some resources if you want to learn more about rate limiting:</p>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/azure/architecture/patterns/rate-limiting-pattern\">Rate Limiting pattern</a></li>\n<li><a href=\"https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/\">Announcing Rate Limiting for .NET</a></li>\n</ul>\n<p>I'm excited to try out rate limiting in my projects.</p>\n<p>That's all for today.</p>\n<p>Have an awesome Saturday!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core",
            "title": "How To Use Rate Limiting In ASP.NET Core",
            "summary": "Rate limiting is a technique to limit the number of requests to a server or an API. ASP.NET Core 7 has a built-in rate limiter middleware that's easy to…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_032.png",
            "date_modified": "2023-04-08T00:00:00.000Z",
            "date_published": "2023-04-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq",
            "content_html": "<p>A <strong>Saga</strong> is a sequence of local transactions, where each local transaction updates the Saga state and publishes a message that triggers the next step.\nYou can implement an orchestrated Saga in .NET with <strong>Rebus</strong>, a free service bus: define the state with <code>ISagaData</code>, inherit from the <code>Saga</code> base class, configure correlation, and use <strong>RabbitMQ</strong> as the message transport.</p>\n<p>Designing long-lived processes in a distributed environment is an interesting engineering challenge.</p>\n<p>And a well known pattern for solving this problem is a <a href=\"https://microservices.io/patterns/data/saga.html\"><strong>Saga</strong></a>.</p>\n<p>A <strong>Saga</strong> is a sequence of local transactions, where each local transaction updates the <strong>Saga</strong> state and publishes a message triggering the next step in the <strong>Saga</strong>.</p>\n<p>Sagas come in two forms:</p>\n<ul>\n<li><strong>Orchestrated</strong></li>\n<li><strong>Choreographed</strong></li>\n</ul>\n<p>With an orchestrated Saga, there's a central component responsible for orchestrating the individual steps.</p>\n<p>In a choreographed Saga, processes work independently but coordinate with each other using events.</p>\n<p>In this week's issue, I'll show you how to create an <strong>orchestrated Saga</strong> using the <strong>Rebus</strong> library with <strong>RabbitMQ</strong> for message transport.</p>\n<p>Let's dive in.</p>\n<h2>Rebus Configuration</h2>\n<p><a href=\"https://github.com/rebus-org/Rebus\">Rebus</a> is a free .NET &quot;service bus&quot;, and it's practical for implementing asynchronous messaging-based\ncommunication between the components of an application.</p>\n<p>Let's install the following libraries:</p>\n<ul>\n<li><code>Rebus.ServiceProvider</code> for managing the <strong>Rebus</strong> instance</li>\n<li><code>Rebus.RabbitMq</code> for <strong>RabbitMQ</strong> message transport</li>\n<li><code>Rebus.SqlServer</code> for <strong>SQL Server</strong> state persistence</li>\n</ul>\n<pre><code class=\"language-powershell\">Install-Package Rebus.ServiceProvider -Version 8.4.0\nInstall-Package Rebus.RabbitMq -Version 8.0.0\nInstall-Package Rebus.SqlServer -Version 7.3.1\n</code></pre>\n<p>Inside of your <strong>ASP.NET Core</strong> application you will need the following configuration.</p>\n<pre><code class=\"language-csharp\">services.AddRebus(\n    rebus =&gt; rebus\n        .Routing(r =&gt;\n            r.TypeBased().MapAssemblyOf&lt;Program&gt;(&quot;newsletter-queue&quot;))\n        .Transport(t =&gt;\n            t.UseRabbitMq(\n                configuration.GetConnectionString(&quot;RabbitMq&quot;),\n                inputQueueName: &quot;newsletter-queue&quot;))\n        .Sagas(s =&gt;\n            s.StoreInSqlServer(\n                configuration.GetConnectionString(&quot;SqlServer&quot;),\n                dataTableName: &quot;Sagas&quot;,\n                indexTableName: &quot;SagaIndexes&quot;))\n        .Timeouts(t =&gt;\n            t.StoreInSqlServer(\n                builder.Configuration.GetConnectionString(&quot;SqlServer&quot;),\n                tableName: &quot;Timeouts&quot;))\n);\n\nservices.AutoRegisterHandlersFromAssemblyOf&lt;Program&gt;();\n</code></pre>\n<p>Unpacking the individual configuration steps:</p>\n<ul>\n<li><code>Routing</code> - Configures messages to be routed by their type</li>\n<li><code>Transport</code> - Configures the message transport mechanism</li>\n<li><code>Sagas</code> - Configures the Saga persistence store</li>\n<li><code>Timeouts</code> - Configures the timeouts persistence store</li>\n</ul>\n<p>You also need to specify the queue name for sending and receiving messages.</p>\n<p><code>AutoRegisterHandlersFromAssemblyOf</code> will scan the specified assembly and automatically register the respective message handlers.</p>\n<h2>Creating The Saga With Rebus</h2>\n<p>We're going to create a <strong>Saga</strong> for a newsletter onboarding process.</p>\n<p>When a user subscribes to the newsletter we want to:</p>\n<ul>\n<li>Send a welcome email immediately</li>\n<li>Send a follow-up email after 7 days</li>\n</ul>\n<p>The first step in creating the <strong>Saga</strong> is defining the data model by implementing <code>ISagaData</code>.\nWe'll keep it simple and store the <code>Email</code> for <strong>correlation</strong>, and add two flags for the distinct steps in our <strong>Saga</strong>.</p>\n<pre><code class=\"language-csharp\">public class NewsletterOnboardingSagaData : ISagaData\n{\n    public Guid Id { get; set; }\n    public int Revision { get; set; }\n\n    public string Email { get; set; }\n\n    public bool WelcomeEmailSent { get; set; }\n\n    public bool FollowUpEmailSent { get; set; }\n}\n</code></pre>\n<p>Now we can define the <code>NewsletterOnboardingSaga</code> class by inheriting from the <code>Saga</code> class and implementing the <code>CorrelateMessages</code> method.</p>\n<p>It's a best practice to use a unique value for correlation.\nIn our case this will be the <code>Email</code>.</p>\n<p>You also configure how the <code>Saga</code> starts with <code>IAmInitiatedBy</code>, and the individual messages the <code>Saga</code> handles with <code>IHandleMessages</code>.</p>\n<pre><code class=\"language-csharp\">public class NewsletterOnboardingSaga :\n    Saga&lt;NewsletterOnboardingSagaData&gt;,\n    IAmInitiatedBy&lt;SubscribeToNewsletter&gt;,\n    IHandleMessages&lt;WelcomeEmailSent&gt;,\n    IHandleMessages&lt;FollowUpEmailSent&gt;\n{\n    private readonly IBus _bus;\n\n    public NewsletterOnboardingSaga(IBus bus)\n    {\n        _bus = bus;\n    }\n\n    protected override void CorrelateMessages(\n        ICorrelationConfig&lt;NewsletterOnboardingSagaData&gt; config)\n    {\n        config.Correlate&lt;SubscribeToNewsletter&gt;(m =&gt; m.Email, d =&gt; d.Email);\n\n        config.Correlate&lt;WelcomeEmailSent&gt;(m =&gt; m.Email, d =&gt; d.Email);\n\n        config.Correlate&lt;FollowUpEmailSent&gt;(m =&gt; m.Email, d =&gt; d.Email);\n    }\n\n    /* Handlers omitted for brevity */\n}\n</code></pre>\n<h2>Message Types And Naming Conventions</h2>\n<p>There are two types of messages you send in a <strong>Saga</strong>:</p>\n<ul>\n<li>Commands</li>\n<li>Events</li>\n</ul>\n<p>Commands instruct the receiving component what to do.<br>\nThink: <strong>verb, imperative.</strong></p>\n<p>Events notify the Saga which process was just completed.<br>\nThink: <strong>what happened, past tense.</strong></p>\n<h2>Saga Orchestration With Messages</h2>\n<p>The <code>NewsletterOnboardingSaga</code> starts by handling the <code>SubscribeToNewsletter</code> command, and publishes a <code>SendWelcomeEmail</code> command.</p>\n<pre><code class=\"language-csharp\">public async Task Handle(SubscribeToNewsletter message)\n{\n    if (!IsNew)\n    {\n        return;\n    }\n\n    await _bus.Send(new SendWelcomeEmail(message.Email));\n}\n</code></pre>\n<p>The <code>SendWelcomeEmail</code> command is handled by a different component, which publishes a <code>WelcomeEmailSent</code> event when it completes.</p>\n<p>In the <code>WelcomeEmailSent</code> handler we update the <code>Saga</code> state and publish a <strong>deferred message</strong> by calling <code>Defer</code>.\nRebus will persist the <code>SendFollowUpEmail</code> command, and publish it when the <strong>timeout expires</strong>.</p>\n<pre><code class=\"language-csharp\">public async Task Handle(WelcomeEmailSent message)\n{\n    Data.WelcomeEmailSent = true;\n\n    await _bus.Defer(TimeSpan.FromDays(3), new SendFollowUpEmail(message.Email));\n}\n</code></pre>\n<p>Finally, the <code>SendFollowUpEmail</code> command is handled and we publish the <code>FollowUpEmailSent</code> event.</p>\n<p>We update the <code>Saga</code> state again, and also call <code>MarkAsComplete</code> to complete the <code>Saga</code>.</p>\n<pre><code class=\"language-csharp\">public Task Handle(FollowUpEmailSent message)\n{\n    Data.FollowUpEmailSent = true;\n\n    MarkAsComplete();\n\n    return Task.CompletedTask;\n}\n</code></pre>\n<p>Completing the <code>Saga</code> will delete it from the database.</p>\n<h2>Handling Commands With Rebus</h2>\n<p>Here's how the <code>SendWelcomeEmail</code> command handler looks like.</p>\n<pre><code class=\"language-csharp\">public class SendWelcomeEmailHandler : IHandleMessages&lt;SendWelcomeEmail&gt;\n{\n    private readonly IEmailService _emailService;\n    private readonly IBus _bus;\n\n    public SendWelcomeEmailHandler(IEmailService emailService, IBus bus)\n    {\n        _emailService = emailService;\n        _bus = bus;\n    }\n\n    public async Task Handle(SendWelcomeEmail message)\n    {\n        await _emailService.SendWelcomeEmailAsync(message.Email);\n\n        await _bus.Reply(new WelcomeEmailSent(message.Email));\n    }\n}\n</code></pre>\n<p>The important thing to highlight here is that we're using the <code>Reply</code> method to send a message back.\nThis will reply back to the endpoint specified as the return address on the current message.</p>\n<h2>In Summary</h2>\n<p><a href=\"https://milanjovanovic.tech/blog/saga-pattern-dotnet\"><strong>Sagas</strong></a> are practical for implementing a long-lived process in a distributed system.\nEach business process is represented by a local transaction, and publishes a message to trigger the next step in the <strong>Saga</strong>.</p>\n<p>Although <strong>Sagas</strong> are very powerful, they are also <em>complicated to develop, maintain and debug.</em></p>\n<p>We didn't cover a few important topics in this newsletter:</p>\n<ul>\n<li>Error handling</li>\n<li><strong>Message retries</strong></li>\n<li>Compensating transactions</li>\n</ul>\n<p>I think you'll have some fun researching these on your own.</p>\n<p>Take a look at the <a href=\"https://github.com/m-jovanovic/newsletter-orchestrated-saga\"><strong>source code for the example used in this newsletter</strong></a>\nif you want to learn more about implementing Sagas.</p>\n<p>If you have <strong>Docker</strong> installed, you should be able to run it without a problem and try it out.</p>\n<p>Thank you for reading, and have an awesome Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/implementing-the-saga-pattern-with-rebus-and-rabbitmq",
            "title": "Implementing The Saga Pattern With Rebus And RabbitMQ",
            "summary": "Designing long-lived processes in a distributed environment is an interesting engineering challenge.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_031.png",
            "date_modified": "2023-04-01T00:00:00.000Z",
            "date_published": "2023-04-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel",
            "content_html": "<p>MediatR v12 added the <code>INotificationPublisher</code> interface, which controls how notification handlers are invoked.\nThe default <code>ForeachAwaitPublisher</code> awaits each handler one by one.\nSwap in <code>TaskWhenAllPublisher</code> through <code>AddMediatR</code>, and the handlers run together with <code>Task.WhenAll</code>, but they still share one service scope.</p>\n<p><strong>MediatR</strong> is a popular library with a simple <strong>mediator pattern</strong> implementation in .NET.</p>\n<p>Here's a definiton taken from MediatR's GitHub: <strong>&quot;In-process messaging with no dependencies.&quot;</strong></p>\n<p>With the rise in popularity of the <a href=\"https://milanjovanovic.tech/blog/cqrs-pattern-with-mediatr\"><strong>CQRS pattern</strong></a>, MediatR became the go-to library to implement commands and queries.</p>\n<p>However, MediatR also has support for the <strong>publish-subscribe</strong> pattern using notifications.\nYou can publish an <code>INotification</code> instance and have multiple subscribers handle the published message.</p>\n<p>Until recently, the handlers subscribing to an <code>INotification</code> message could only execute serially, one by one.</p>\n<p>In this week's newsletter, I'll show you how to configure MediatR to <strong>execute the handlers in parallel</strong>.</p>\n<p>Let's dive in.</p>\n<h2>How Publish-Subscribe Works With MediatR</h2>\n<p>Before I talk about notification publishing strategies, let's see how <strong>publish-subscribe</strong> works with <strong>MediatR</strong>.</p>\n<p>You need a class implementing the <code>INotification</code> interface:</p>\n<pre><code class=\"language-csharp\">public record OrderCreated(Guid OrderId) : INotification;\n</code></pre>\n<p>Then you need a respective <code>INotificationHandler</code> implementation:</p>\n<pre><code class=\"language-csharp\">public class OrderCreatedHandler : INotificationHandler&lt;OrderCreated&gt;\n{\n    private readonly INotificationService _notificationService;\n\n    public OrderCreatedHandler(INotificationService notificationService)\n    {\n        _notificationService = notificationService;\n    }\n\n    public async Task Handle(\n        OrderCreated notification,\n        CancellationToken cancellationToken)\n    {\n        await _notificationService.SendOrderCreatedEmail(\n            notification.OrderId,\n            cancellationToken);\n    }\n}\n</code></pre>\n<p>And then you simply publish a message using either <code>IMediator</code> or <code>IPublisher</code>.\nI prefer using the <code>IPublisher</code> because it's more expressive:</p>\n<pre><code class=\"language-csharp\">await publisher.Publish(new OrderCreated(order.Id), cancellationToken);\n</code></pre>\n<p>MediatR will invoke all the respective handlers.</p>\n<h2>Introducing Notification Publisher Strategies</h2>\n<p>Before MediatR v12, the publishing strategy would invoke each handler individually.</p>\n<p>However, there's a new interface <code>INotificationPublisher</code> controlling how the handlers are called.</p>\n<p>The default implementation of this interface is <code>ForeachAwaitPublisher</code>:</p>\n<pre><code class=\"language-csharp\">public class ForeachAwaitPublisher : INotificationPublisher\n{\n    public async Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken)\n    {\n        foreach (var handler in handlerExecutors)\n        {\n            await handler\n                .HandlerCallback(notification, cancellationToken)\n                .ConfigureAwait(false);\n        }\n    }\n}\n</code></pre>\n<p>But now you can also use the <code>TaskWhenAllPublisher</code>:</p>\n<pre><code class=\"language-csharp\">public class TaskWhenAllPublisher : INotificationPublisher\n{\n    public Task Publish(\n        IEnumerable&lt;NotificationHandlerExecutor&gt; handlerExecutors,\n        INotification notification,\n        CancellationToken cancellationToken)\n    {\n        var tasks = handlerExecutors\n            .Select(handler =&gt; handler.HandlerCallback(\n                notification,\n                cancellationToken))\n            .ToArray();\n\n        return Task.WhenAll(tasks);\n    }\n}\n</code></pre>\n<p>Here's a comparison between these two strategies.</p>\n<p><code>ForeachAwaitPublisher</code>:</p>\n<ul>\n<li>Invokes each handler one by one</li>\n<li>Fails when an exception occurs in one of the handlers</li>\n</ul>\n<p><code>TaskWhenAllPublisher</code>:</p>\n<ul>\n<li>Invokes all the handlers at the same time</li>\n<li>Executes all the handlers regardless of one of them throwing an exception</li>\n</ul>\n<p>If you store the task returned by <code>TaskWhenAllPublisher</code> you can access the <code>Task.Exception</code> property, which will contain an <code>AggregateException</code> instance.\nYou can then implement more robust exception handling.</p>\n<h2>Configuring MediatR Notification Publishing Strategy</h2>\n<p>How do we configure which <code>INotificationPublisher</code> strategy MediatR will use?</p>\n<p>There's a new way to apply configuration options when calling the <code>AddMediatR</code> method.</p>\n<p>You supply an <code>Action&lt;MediatRServiceConfiguration&gt;</code> delegate and configure the <code>MediatRServiceConfiguration</code> instance.</p>\n<p>If you want to use the <code>TaskWhenAllPublisher</code> strategy, you can either:</p>\n<ul>\n<li>Provide a value for the <code>NotificationPublisher</code> property</li>\n<li>Specify the strategy type on the <code>NotificationPublisherType</code> property</li>\n</ul>\n<pre><code class=\"language-csharp\">services.AddMediatR(config =&gt; {\n    config.RegisterServicesFromAssemblyContaining&lt;Program&gt;();\n\n    // Setting the publisher directly will make the instance a Singleton.\n    config.NotificationPublisher = new TaskWhenAllPublisher();\n\n    // Seting the publisher type will:\n    // 1. Override the value set on NotificationPublisher\n    // 2. Use the service lifetime from the ServiceLifetime property below\n    config.NotificationPublisherType = typeof(TaskWhenAllPublisher);\n\n    config.ServiceLifetime = ServiceLifetime.Transient;\n});\n</code></pre>\n<p>You can also implement a custom <code>INotificationPublisher</code> instance and use your own implementation instead.</p>\n<h2>How Is This Useful?</h2>\n<p>Being able to <strong>run notification handlers in parallel</strong> provides a significant <strong>performance improvement</strong> over the default behavior.</p>\n<p>However, note that all handlers will use the same service scope.</p>\n<p>If you have service instances that don't support concurrent access you may run into problems.</p>\n<p>Unfortunately, one such service instance is the <strong>EF Core</strong> <code>DbContext</code>.</p>\n<p>In any case, I think this is a great addition to the already amazing <strong>MediatR</strong> library.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-publish-mediatr-notifications-in-parallel",
            "title": "How To Publish MediatR Notifications In Parallel",
            "summary": "MediatR supports the publish-subscribe pattern with notifications, and until recently the handlers subscribing to an INotification could only execute serially…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_030.png",
            "date_modified": "2023-03-25T00:00:00.000Z",
            "date_published": "2023-03-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit",
            "content_html": "<p>xUnit gives you four ways to feed data into a parameterized test: <code>InlineData</code>, <code>MemberData</code>, <code>ClassData</code>, and <code>TheoryData</code>.\n<code>InlineData</code> takes constant values in the attribute, and the other three load the data from a member or a class.\n<code>TheoryData</code> is the one that gives you type safety, since <code>MemberData</code> and <code>ClassData</code> both hand back untyped <code>object[]</code>.</p>\n<p><strong>Data-driven testing</strong> is a testing method where test data is provided through some external source.\nHence it's also known as <strong>parameterized testing</strong>.</p>\n<p>A popular testing library in .NET that supports parameterized testing is <strong>xUnit</strong>.\nIt uses attributes to define test methods.\nThe <code>Fact</code> attribute defines a simple test, and the <code>Theory</code> attribute defines a parameterized test.</p>\n<p>In this week's newsletter, I'm going to show you four ways to write <strong>parameterized tests</strong> with xUnit:</p>\n<ul>\n<li><code>InlineData</code></li>\n<li><code>MemberData</code></li>\n<li><code>ClassData</code></li>\n<li><code>TheoryData</code></li>\n</ul>\n<p>And I'll discuss which approach I think is the best.</p>\n<p>Let's dive in.</p>\n<h2>Writing Parameterized Tests With InlineData</h2>\n<p>The simplest way to write parameterized tests with xUnit is using the <code>InlineData</code> attribute.\nYou provide test data by passing in values to the <code>InlineData</code> constructor.</p>\n<p>Here's how that would look like:</p>\n<pre><code class=\"language-csharp\">[Theory]\n[InlineData(&quot;test@test.com&quot;, &quot;test.com&quot;)]\n[InlineData(&quot;milan@milanjovanovic.tech&quot;, &quot;milanjovanovic.tech&quot;)]\npublic void EmailParser_Should_Return_Domain(string email, string expectedDomain)\n{\n    // Arrange\n    var parser = new EmailParser();\n\n    // Act\n    var domain = parser.ParseDomain(email);\n\n    // Assert\n    Assert.Equal(domain, expectedDomain);\n}\n</code></pre>\n<p>In this example, we provide two <code>string</code> values to the <code>InlineData</code> attribute, which represent the <code>email</code> and <code>expectedDomain</code> parameters in the test.\nWe can specify the <code>InlineData</code> attribute as many times as we want, to introduce more test cases.</p>\n<p>The downside of this approach is that it becomes very verbose when we have many test cases.\nAnd we are limited to only using constant data for the parameters.</p>\n<h2>Writing Parameterized Tests With MemberData</h2>\n<p>With the <code>MemberData</code> attribute we have the ability to programmatically provide the test data.\nYou can load the test data from a static property or member of a type.</p>\n<p>Here's an example of using the <code>MemberData</code> attribute to load test data from a property:</p>\n<pre><code class=\"language-csharp\">[Theory]\n[MemberData(nameof(EmailTestData))]\npublic void EmailParser_Should_Return_Domain(string email, string expectedDomain)\n{\n    // Arrange\n    var parser = new EmailParser();\n\n    // Act\n    var domain = parser.ParseDomain(email);\n\n    // Assert\n    Assert.Equal(domain, expectedDomain);\n}\n\npublic static IEnumerable&lt;object[]&gt; EmailTestData =&gt; new List&lt;object&gt;\n{\n    new object[] { &quot;test@test.com&quot;, &quot;test.com&quot; },\n    new object[] { &quot;milan@milanjovanovic.tech&quot;, &quot;milanjovanovic.tech&quot; }\n};\n</code></pre>\n<p>You specify the name of the member in the <code>MemberData</code> attribute, and it's a best practice to use the <code>nameof</code> operator\nso that you can rename the property (or method) in the future without breaking your test.</p>\n<p>The one constraint is that the property (or method) has to return <code>IEnumerable&lt;object[]&gt;</code>, so there is no strong typing.</p>\n<h2>Writing Parameterized Tests With ClassData</h2>\n<p>The <code>ClassData</code> attribute allows you to extract test data into its own class.\nThis is helpful for organizing your test data separately from your tests, and it allows for easier reuse.\nYou load the test from a class the inherits from <code>IEnumerable&lt;object[]&gt;</code> and implements the <code>GetEnumerator</code> method.</p>\n<p>Here's an example of using the <code>ClassData</code> attribute to load test data from a class:</p>\n<pre><code class=\"language-csharp\">[Theory]\n[ClassData(typeof(EmailTestData))]\npublic void EmailParser_Should_Return_Domain(string email, string expectedDomain)\n{\n    // Arrange\n    var parser = new EmailParser();\n\n    // Act\n    var domain = parser.ParseDomain(email);\n\n    // Assert\n    Assert.Equal(domain, expectedDomain);\n}\n\npublic class EmailTestData : IEnumerable&lt;object[]&gt;\n{\n\n    public IEnumerable&lt;object[]&gt; GetEnumerator()\n    {\n        yield return new object[] { &quot;test@test.com&quot;, &quot;test.com&quot; };\n        yield return new object[] { &quot;milan@milanjovanovic.tech&quot;, &quot;milanjovanovic.tech&quot; };\n    }\n\n    IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();\n};\n</code></pre>\n<p>Unfortunately, this approach is complicated because you have to implement the <code>IEnumerable</code> interface.</p>\n<p>It almost defeats the purpose of separating test data from the actual tests.</p>\n<p>And we still suffer from lack of type-safety.</p>\n<p>Is there a better solution?</p>\n<h2>Writing Parameterized Tests With TheoryData</h2>\n<p>Let me introduce you to <code>TheoryData</code>, which is my preferred way of providing test data for parameterized tests.\nUsing the <code>TheoryData</code> class you can implement a class to provide test data while having the benefit of type-safety.</p>\n<p>Here's an example of using <code>TheoryData</code> in combination with <code>ClassData</code>:</p>\n<pre><code class=\"language-csharp\">[Theory]\n[ClassData(typeof(EmailTestData))]\npublic void EmailParser_Should_Return_Domain(string email, string expectedDomain)\n{\n    // Arrange\n    var parser = new EmailParser();\n\n    // Act\n    var domain = parser.ParseDomain(email);\n\n    // Assert\n    Assert.Equal(domain, expectedDomain);\n}\n\npublic class EmailTestData : TheoryData&lt;string, string&gt;\n{\n    public EmailTestData()\n    {\n        Add(&quot;test@test.com&quot;, &quot;test.com&quot;);\n        Add(&quot;milan@milanjovanovic.tech&quot;, &quot;milanjovanovic.tech&quot;);\n    }\n};\n</code></pre>\n<p>How does <code>TheoryData</code> work?</p>\n<p>It's a generic class that allows us to specify the types for our parameterized test.</p>\n<p>You just call the <code>Add</code> method in the <code>EmailTestData</code> constructor to provide test data for a single test case.\nAnd introducing more test cases comes down to calling the <code>Add</code> method multiple times.</p>\n<p>You can also use <code>TheoryData</code> in combination with <code>MemberData</code>, and return <code>TheoryData</code> from a property or method.</p>\n<h2>Which Approach Should You Use?</h2>\n<p>I showed you four approaches to write parameterized tests with xUnit:</p>\n<ul>\n<li><code>InlineData</code></li>\n<li><code>MemberData</code></li>\n<li><code>ClassData</code></li>\n<li><code>TheoryData</code></li>\n</ul>\n<p>So which one should you use?</p>\n<p>Here's my personal preference that you can follow if you want:</p>\n<ul>\n<li><code>InlineData</code> for simple test cases</li>\n<li><code>TheoryData</code> using <code>ClassData</code> for complex test cases</li>\n</ul>\n<p>Thank you for reading, and have a wonderful Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/creating-data-driven-tests-with-xunit",
            "title": "Creating Data-Driven Tests With xUnit",
            "summary": "Data-driven testing is where test data comes from an external source, which is why it's also known as parameterized testing.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_029.png",
            "date_modified": "2023-03-18T00:00:00.000Z",
            "date_published": "2023-03-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application",
            "content_html": "<p>You can register more than one <code>DbContext</code> in a single application by calling <code>AddDbContext</code> once per context.\nUse it when the app spans multiple databases, when you want a read-replica context, or to give each module of a modular monolith its own schema with <code>HasDefaultSchema</code>.\nJoins and shared transactions across contexts are limited.</p>\n<p><strong>Entity Framework Core (EF Core)</strong> is a popular ORM in .NET that allows you to work with SQL databases.\nEF Core uses a <code>DbContext</code>, which represents a session with the database and is responsible for tracking changes,\nperforming database operations, and managing database connections.</p>\n<p>It's common to have only one <code>DbContext</code> for the entire application.</p>\n<p>But what if you need to have <strong>multiple DbContexts</strong>?</p>\n<p>In this week's newsletter we're going to explore:</p>\n<ul>\n<li>When you may want to use multiple DbContexts</li>\n<li>How to create multiple DbContexts</li>\n<li>What are the benefits of using multiple DbContexts</li>\n</ul>\n<p>Let's dive in!</p>\n<h2>Why Use Multiple DbContexts?</h2>\n<p>There are a few cases where using <strong>multiple DbContexts can be useful.</strong></p>\n<p><strong>Multiple Databases</strong><br>\nDoes your application need to work with multiple SQL databases?\nThen you're forced to use multiple DbContexts, each one dedicated to a specific SQL database.</p>\n<p><strong>Separating Concerns</strong><br>\nIf the application you're building has a complex domain model, you may see an improvement by separating concerns\nbetween a few DbContexts, where each one is responsible for a specific area of the domain model.</p>\n<p><strong>Modular Monolith</strong><br>\nWhen you're building a <a href=\"https://milanjovanovic.tech/blog/modular-monolith-architecture-dotnet\"><strong>Modular Monolith</strong></a>, using multiple DbContexts can be practical because you can configure\na different <strong>database schema</strong> per <code>DbContext</code>, giving you logical separation at the database level.</p>\n<p><strong>Read Replicas</strong><br>\nYou can configure a separate <code>DbContext</code> instance to access the read replica of your database, and use that\n<code>DbContext</code> for read-only queries.\nYou can also configure <code>QueryTrackingBehavior.NoTracking</code> on the <code>DbContext</code> level to <a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>improve query performance</strong></a>.</p>\n<h2>Creating Multiple DbContexts In a Single Application</h2>\n<p>Here's how you can easily configure multiple DbContexts.\nLet's say we have a <code>CatalogDbContext</code> and an <code>OrderDbContext</code> in our application.\nWe want to configure them using the following constrainsts:</p>\n<ul>\n<li>Both DbContexts use the <strong>same database</strong></li>\n<li>Each DbContext has a <strong>separate database schema</strong></li>\n</ul>\n<pre><code class=\"language-csharp\">public class CatalogDbContext : DbContext\n{\n    public DbSet&lt;Product&gt; Products { get; set; }\n\n    public DbSet&lt;Category&gt; Categories { get; set; }\n}\n\npublic class OrderDbContext : DbContext\n{\n    public DbSet&lt;Order&gt; Orders { get; set; }\n\n    public DbSet&lt;LineItem&gt; LineItems { get; set; }\n}\n</code></pre>\n<p>First we need to configure the <code>CatalogDbContext</code> and <code>OrderDbContext</code> with the DI container.\nYou can do this by calling the <code>AddDbContext</code> method and specifying which <code>DbContext</code> is being configured,\nand then using the SQL provider specific method to pass the connection string.\nIn this case I'm connecting to SQL Server with the <code>UseSqlServer</code> method.</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\n\nservices.AddDbContext&lt;CatalogDbContext&gt;(options =&gt;\n    options.UseSqlServer(&quot;CONNECTION_STRING&quot;));\n\nservices.AddDbContext&lt;OrderDbContext&gt;(options =&gt;\n    options.UseSqlServer(&quot;CONNECTION_STRING&quot;));\n</code></pre>\n<p>If you just want to use both DbContexts in the same schema, then this is all the configuration you need.\nYou can now inject the <code>DbContext</code> instances in your application and use them.</p>\n<p>However, if you want to configure a different schema for each <code>DbContext</code> then you also need to override\nthe <code>OnModelCreating</code> method and specify the custom schema with <code>HasDefaultSchema</code>.</p>\n<pre><code class=\"language-csharp\">public class CatalogDbContext : DbContext\n{\n    public DbSet&lt;Product&gt; Products { get; set; }\n\n    public DbSet&lt;Category&gt; Categories { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.HasDefaultSchema(&quot;catalog&quot;);\n    }\n}\n\npublic class OrderDbContext : DbContext\n{\n    public DbSet&lt;Order&gt; Orders { get; set; }\n\n    public DbSet&lt;LineItem&gt; LineItems { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.HasDefaultSchema(&quot;order&quot;);\n    }\n}\n</code></pre>\n<p>Limitations with multiple DbContexts:</p>\n<ol>\n<li>It's not possible to do a join between different <code>DbContext</code> instances, because <strong>EF Core</strong> doesn't know if they\nare using the same database</li>\n<li>Transactions will only work if the DbContexts are using the same database. You have to create a new transaction\nand share it between the DbContexts by <a href=\"https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core#using-existing-transactions-with-ef-core\">calling the <code>UseTransaction</code> method</a></li>\n</ol>\n<p><strong>Migrations History Table</strong></p>\n<p>If you decide to use different schemas per <code>DbContext</code>, you will be unpleasantly surprised to learn\nthat the default schema doesn't apply to the migrations history table.</p>\n<p>You need to configure this by calling the <code>MigrationsHistoryTable</code> method and specifying the table name\nand schema where the migrations history for that context will live.\nI used the <code>HistoryRepository.DefaultTableName</code> constant in this example, but you can specify a custom\ntable name if you want to.</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nservices.AddDbContext&lt;CatalogDbContext&gt;(options =&gt;\n    options.UseSqlServer(\n        &quot;CONNECTION_STRING&quot;,\n        o =&gt; o.MigrationsHistoryTable(\n            tableName: HistoryRepository.DefaultTableName,\n            schema: &quot;catalog&quot;)));\n\nservices.AddDbContext&lt;OrderDbContext&gt;(options =&gt;\n    options.UseSqlServer(\n        &quot;CONNECTION_STRING&quot;,\n        o =&gt; o.MigrationsHistoryTable(\n            tableName: HistoryRepository.DefaultTableName,\n            schema: &quot;order&quot;)));\n</code></pre>\n<h2>Benefits Of Using Multiple DbContexts</h2>\n<p>Using multiple DbContexts can offer several benefits to your application:</p>\n<ul>\n<li>Separation of concerns</li>\n<li>Better performance</li>\n<li>More control &amp; security</li>\n</ul>\n<p>Each DbContext can be responsible for a specific subset of the application's data, which can help organize the code\nand make it more modular.</p>\n<p>When you separate data access into multiple DbContexts, the application can reduce the risk of contention and improve\nconcurrency, and this can improve performance.</p>\n<p>And if you're using multiple DbContexts, you can configure more granular access control to improve application security.\nYou can also optimize performance and resource usage.</p>\n<h2>In Summary</h2>\n<p>Using <strong>multiple EF Core DbContexts</strong> in a single application is straightforward and has many benefits.</p>\n<p>For ready-heavy applications you can configure a separate <code>DbContext</code> to turn off query tracking by default\nand get improved performance.</p>\n<p>Also, using multiple DbContexts is practical if you're building a <strong>Modular monolith</strong>.\nYou can configure the DbContexts to be in <strong>separate database schemas</strong>, giving you logical separation at the database level.</p>\n<p>That's all for today.</p>\n<p>See you next week.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application",
            "title": "Using Multiple EF Core DbContexts In a Single Application",
            "summary": "It's common to have only one DbContext for the entire application. But what if you need multiple DbContexts?",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_028.png",
            "date_modified": "2023-03-11T00:00:00.000Z",
            "date_published": "2023-03-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-apply-functional-programming-in-csharp",
            "content_html": "<p>C# is object-oriented, but recent versions added functional features like pattern matching, switch expressions, and records.\nIf you use LINQ, you are already writing functional code.\nYou apply it by capturing each step as a pure <code>Func</code> delegate and composing those functions, instead of writing the steps out imperatively.</p>\n<p>Although <strong>C#</strong> is an <strong>object-oriented programming</strong> language, it received many new functional features in recent versions.</p>\n<p>To mention just a few of these features:</p>\n<ul>\n<li>Pattern matching</li>\n<li>Switch expressions</li>\n<li><a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>Records</strong></a></li>\n</ul>\n<p>You're probably already doing <strong>functional programming</strong> without even knowing it.</p>\n<p>Do you use LINQ?\nIf you do, then you're doing functional programming.\nBecause LINQ is a functional .NET library.</p>\n<p>In today's issue, I will show you how to <strong>refactor</strong> some <strong>imperative code</strong> with <strong>functional programming</strong>.</p>\n<p>Let's dive in.</p>\n<h2>Benefits Of Functional Programming</h2>\n<p>Before we take a look at some code, let's see what are the <strong>benefits</strong> of using <strong>functional programming</strong>:</p>\n<ul>\n<li>Emphasis on immutability</li>\n<li>Emphasis on function purity</li>\n<li>Code is easier to reason about</li>\n<li>Less prone to bugs and errors</li>\n<li>Ability to compose functions and create higher-order functions</li>\n<li>Easier to test and debug</li>\n</ul>\n<p>From my experience, I think functional programming is more enjoyable once you get used to it.\nStarting out, it feels strange because your old object-oriented programming habits will kick in.\nBut after a while, <strong>functional programming</strong> feels easier to work with than imperative code.</p>\n<h2>Starting With Imperative Code</h2>\n<p><strong>Imperative programming</strong> is the most basic programming approach.\nWe describe a step-by-step process to execute a program.\nIt's easier for beginners to reason with imperative code by following along with the steps in the process.</p>\n<p>Here's an example of an <code>EmailValidator</code> class written with imperative code:</p>\n<pre><code class=\"language-csharp\">public class EmailValidator\n{\n    private const int MaxLength = 255;\n\n    public (bool IsValid, string? Error) Validate(string email)\n    {\n        if (string.IsNullOrEmpty(email))\n        {\n            return (false, &quot;Email is empty&quot;);\n        }\n\n        if (email.Length &gt; MaxLength)\n        {\n            return (false, &quot;Email is too long&quot;);\n        }\n\n        if (email.Split('@').Length != 2)\n        {\n            return (false, &quot;Email format is invalid&quot;);\n        }\n\n        if (Uri.CheckHostName(email.Split('@')[1]) == UriHostNameType.Unknown)\n        {\n            return (false, &quot;Email domain is invalid&quot;);\n        }\n\n        return (true, null);\n    }\n}\n</code></pre>\n<p>You can clearly see the distinct steps:</p>\n<ul>\n<li>Check if email is null or empty</li>\n<li>Check if email is not too long</li>\n<li>Check if email format is valid</li>\n<li>Check if email domain is valid</li>\n</ul>\n<p>Let's see how we can refactor this using <strong>functional programming</strong>.</p>\n<h2>Applying Functional Programming</h2>\n<p>The basic building block in <strong>functional programming</strong> is - <strong>a function</strong>.\nAnd programs are written by composing function calls.\nThere are a few other things you need to keep in mind, like keeping your functions pure.\nA function is pure if it always returns the same output for the same input.</p>\n<p>We can capture each step from the imperative version of <code>EmailValidator</code> with a <code>Func</code> delegate.\nTo also capture the respective error message together with the validation check, we can use a tuple.\nAnd since we know all of our validation steps, we can create an array of <code>Func</code> delegates to store all of the individual <strong>functions</strong>.</p>\n<pre><code class=\"language-csharp\">public class EmailValidator\n{\n    const int MaxLength = 255;\n\n    static readonly Func&lt;string, (bool IsValid, string? Error)&gt;[] _validations =\n    {\n        email =&gt; (!string.IsNullOrEmpty(email), &quot;Email is empty&quot;),\n        email =&gt; (email.Length &lt;= MaxLength, &quot;Email is too long&quot;),\n        email =&gt; (email.Split('@').Length == 2, &quot;Email format is invalid&quot;),\n        email =&gt; (\n            Uri.CheckHostName(email.Split('@')[1]) != UriHostNameType.Unknown,\n            &quot;Email domain is invalid&quot;)\n    };\n\n    static readonly (bool IsValid, string? Error) _successResult = (true, null);\n\n    public (bool IsValid, string? Error) Validate(string email)\n    {\n        var validationResult = _validations\n            .Select(func =&gt; func(email))\n            .FirstOrDefault(func =&gt; !func.IsValid);\n\n        return validationResult is { IsValid: false, Error: { Length: &gt;0 } } ?\n            validationResult : _successResult;\n    }\n}\n</code></pre>\n<p>Notice that this allows us to do all sorts of interesting things with the <code>_validations</code> array.\nHow hard would it be to modify this function to return <em>all of the errors</em> instead of just the first one?</p>\n<p>If you're thinking we can use LINQ's <code>Select</code> method somehow, you're thinking in the right direction.</p>\n<h2>Further Reading</h2>\n<p>We only scratched the surface of what functional programming is, and what you can do with it.\nIf you want to learn more, here are some learning materials:</p>\n<ul>\n<li><a href=\"https://www.manning.com/books/functional-programming-in-c-sharp\">Functional Programming in C#, by Enrico Buonanno</a></li>\n<li><a href=\"https://youtu.be/dDasAmowFts\">Functional Programming With C# Using Railway-Oriented Programming</a></li>\n<li><a href=\"https://youtu.be/zuy2j8vxgYc\">How Function Composition Can Make Your Code Better</a></li>\n<li><a href=\"https://youtu.be/AVA2mKG4WOc\">Make Your ASP.NET Core API Controllers Incredibly Simple With Functional Programming</a></li>\n</ul>\n<p>Thank you for reading, and have a wonderful Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-apply-functional-programming-in-csharp",
            "title": "How To Apply Functional Programming In C#",
            "summary": "Although C# is object-oriented, it has plenty of functional features: pattern matching, switch expressions, records.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_027.png",
            "date_modified": "2023-03-04T00:00:00.000Z",
            "date_published": "2023-03-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging",
            "content_html": "<p>The Outbox pattern stores outgoing messages in an <code>OutboxMessages</code> table, in the same database transaction that saves your entities.\nA background process polls the table, publishes any unprocessed message, and retries when publishing fails.\nNothing is lost when the message bus is down, because the message is already committed with the data.</p>\n<p>Working with <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>Microservices</strong></a>, or any distributed system for that matter, is difficult.\nIn a distributed system many things can go wrong, and there are even research papers about this.\nIf you want to explore this topic further, I suggest that you read about the <a href=\"https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing\">fallacies of distributed computing</a>.</p>\n<p>Reducing the surface area for things to go wrong should be one of your goals, as an engineer.\nIn this week's newsletter, we'll try to achieve exactly that using the <strong>Outbox pattern</strong>.</p>\n<p>How can you implement reliable communication between components in a distributed system?</p>\n<p>The <strong>Outbox pattern</strong> is an elegant solution to this problem, allowing you to achieve transactional\nguarantees in a single service and at-least-once message delivery to external systems.</p>\n<p>Let's see how the <strong>Outbox pattern</strong> solves this and how can we implement it.</p>\n<h2>What Problem Does The Outbox Pattern Solve?</h2>\n<p>To understand what problem the <strong>Outbox pattern</strong> solves, first we need a problem, of course.</p>\n<p>Here's an example of a user registration flow.\nThere are a few things going on here:</p>\n<ul>\n<li>Saving the <code>User</code> to the database</li>\n<li>Sending a welcome email to the <code>User</code></li>\n<li>Publishing a <code>UserRegisteredEvent</code> to a message bus</li>\n</ul>\n<pre><code class=\"language-csharp\">public async Task RegisterUserAsync(User user, CancellationToken token)\n{\n    _userRepository.Insert(user);\n\n    await _unitOfWork.SaveChangesAsync(token);\n\n    await _emailService.SendWelcomeEmailAsync(user, token);\n\n    await _eventBus.PublishAsync(new UserRegisteredEvent(user.Id), token);\n}\n</code></pre>\n<p>In the happy path, all of the operations complete without any issues and all is well.</p>\n<p>But what happens if any one of these operations fail?</p>\n<ul>\n<li>The database is unavailable, and saving the <code>User</code> fails</li>\n<li>The email service is down and sending an email crashes</li>\n<li>Publishing an event to the service bus doesn't succeed</li>\n</ul>\n<p>Also, imagine a situation where you manage to save a <code>User</code> to the database,\nsend him a welcome email, but fail to publish the <code>UserRegisteredEvent</code> to notify other services.\nHow are you going to recover from this scenario?</p>\n<p>The <strong>Outbox pattern</strong> allows you to <strong>atommically</strong> update the database and send messages to the message bus.</p>\n<h2>Implementing The Outbox Pattern</h2>\n<p>The first step is to introduce a table in your database to represent the <strong>Outbox</strong>.\nWe can call this table <code>OutboxMessages</code>, and it's intended to store all messages that need to be delivered.\nNow instead of directly making requests to external services, we simply store a message as a new row in the <strong>Outbox</strong> table.\nThe messages are usually stored as JSON in the database.</p>\n<p>The second step is to introduce a <strong>background process</strong> that will periodically poll the <code>OutboxMessages</code> table.\nIf the worker process finds a row with an unprocessed message, it's going to publish that message and mark it as sent.\nIf publishing the message fails for some reason, the work process can <strong>retry</strong> in the next execution.</p>\n<p>Notice that with retries, you now have <strong>at-least-once message delivery</strong> implemented.\nThe message will be published exactly once for the happy path, and more than one time in case or retries.</p>\n<p>We can rewrite the <code>RegisterUserAsync</code> method from the previous example, now using an <strong>Outbox</strong>:</p>\n<pre><code class=\"language-csharp\">public async Task RegisterUserAsync(User user, CancellationToken token)\n{\n    _userRepository.Insert(user);\n\n    _outbox.Insert(new UserRegisteredEvent(user.Id));\n\n    await _unitOfWork.SaveChangesAsync(token);\n}\n</code></pre>\n<p>The <strong>Outbox</strong> is part of the same transaction as our unit of work, so we can atomically save the <code>User</code> to the database\nand also persist the <code>OutboxMessage</code>.\nIf saving to the database fails, the entire transaction is rolled back and no messages are sent to the message bus.</p>\n<p>And since we now moved the publishing of the <code>UserRegisteredEvent</code> to the worker process, we need to add a handler\nso that we can send the welcome email to the user.\nHere's an example of that in the <code>SendWelcomeEmailHandler</code> class:</p>\n<pre><code class=\"language-csharp\">public class SendWelcomeEmailHandler : IHandle&lt;UserRegisteredEvent&gt;\n{\n    private readonly IUserRepository _userRepository;\n    private readonly IEmailService _emailService;\n\n    public SendWelcomeEmailHandler(\n        IUserRepository userRepository,\n        IEmailService emailService)\n    {\n        _userRepository = userRepository;\n        _emailService = emailService;\n    }\n\n    public async Task Handle(UserRegisteredEvent message)\n    {\n        var user = await _userRepository.GetByIdAsync(message.UserId);\n\n        await _emailService.SendWelcomeEmailAsync(user);\n    }\n}\n</code></pre>\n<h2>Architecture Diagram With Outbox</h2>\n<p>Here's a high level overview of the system architecture with the <strong>Outbox</strong> introduced to the system.\nYou can see the <code>Outbox</code> table in the database.\nWhat changes now is that you store messages to the <code>Outbox</code> table in the same transaction along with your entities.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_026/outbox.png\" alt=\"Transactional Outbox flow: save the user and message together, then publish the message from a worker\">\n<h2>Further Reading</h2>\n<p>After reading this newsletter you should have a pretty good understanding of what the <strong>Outbox pattern</strong> is\nand what problems it solves.\nIf you need to implement reliable messaging in a distributed system, it's a great solution for your problem.</p>\n<p>What's missing is more details about how to implement the <strong>Outbox pattern</strong>, so here are a few videos you can watch:</p>\n<ul>\n<li><a href=\"https://youtu.be/BimfDeDV4yU\">How to use the Domain Events pattern</a></li>\n<li><a href=\"https://youtu.be/XALvnX7MPeo\">How to implement the Outbox pattern</a></li>\n<li><a href=\"https://youtu.be/xajVttkZntU\">How to add retries to the Outbox with Polly</a></li>\n</ul>\n<p>Thanks for reading, and have an amazing Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging",
            "title": "Outbox Pattern For Reliable Microservices Messaging",
            "summary": "How can you implement reliable communication between components in a distributed system? The Outbox pattern is an elegant solution, giving you transactional…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_026.png",
            "date_modified": "2023-02-25T00:00:00.000Z",
            "date_published": "2023-02-25T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/structured-logging-in-asp-net-core-with-serilog",
            "content_html": "<p>Serilog gives ASP.NET Core structured logging out of the box.\nInstall the <code>Serilog.AspNetCore</code> package, call <code>UseSerilog</code> on the host builder, and configure the sinks in <code>appsettings.json</code>.\nThen log with message templates like <code>{@Book}</code> so the values are stored as searchable properties, not flattened into text.</p>\n<p><strong>Structured logging</strong> is a practice where you apply the same message format to all of your\napplication logs.\nThe end result is that all your logs will have a similar structure, allowing them to be\neasily searched and analyzed.</p>\n<p><a href=\"https://serilog.net/\">Serilog</a> is a popular logging library in .NET, packed with many features.\nIt provides logging to files, logging to the console, and elsewhere.</p>\n<p>However, <strong>Serilog</strong> is unique because it comes with support for <strong>structured logging</strong> out of the box.</p>\n<p>Let's see how we can install <strong>Serilog</strong> and configure it an <strong>ASP.NET Core</strong> application.</p>\n<h2>Installing Serilog</h2>\n<p>To install <strong>Serilog</strong> in <strong>ASP.NET Core</strong> you can add the following NuGet package:</p>\n<pre><code class=\"language-powershell\">Install-Package Serilog.AspNetCore\n</code></pre>\n<p>This NuGet packages comes with a simple API to integrate <strong>Serilog</strong> into your application.\nYou can call the <code>UseSerilog</code> method on the <code>HostBuilder</code> instance to provide a lambda\nmethod to configure <strong>Serilog</strong>.</p>\n<p>I think the most flexible way to configure Serilog is through application settings,\nwhich is achieved by calling <code>ReadFrom.Configuration()</code>.</p>\n<p>You can also call the <code>UseSerilogRequestLogging()</code> method to introduce automatic HTTP request logging\nin your API.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Host.UseSerilog((context, configuration) =&gt;\n    configuration.ReadFrom.Configuration(context.Configuration));\n\nvar app = builder.Build();\n\napp.UseSerilogRequestLogging();\n\napp.Run();\n</code></pre>\n<p>The next question is how do you provide the actual configuration values to <strong>Serilog</strong>?</p>\n<h2>Configuring Serilog With <code>appsettings.json</code></h2>\n<p>You need to add a <code>Serilog</code> section in your <code>appsettings.json</code> file.</p>\n<p>Here you can configure, among other things:</p>\n<ul>\n<li>Which <strong>sinks</strong> to use with <strong>Serilog</strong></li>\n<li>Override default and minimum log levels</li>\n<li>Configure file logging arguments</li>\n</ul>\n<p>In this example, we're adding the <code>Console</code> and <code>File</code> sinks to <strong>Serilog</strong>.\nAnd we're adding some additional configuration for the <code>File</code> sink in the <code>Serilog.WriteTo</code> configuration section.\nWe can configure the output path for the log files, the naming format, which formatter to use for the logs and so on.</p>\n<pre><code class=\"language-json\">&quot;Serilog&quot;: {\n  &quot;Using&quot;: [ &quot;Serilog.Sinks.Console&quot;, &quot;Serilog.Sinks.File&quot; ],\n  &quot;MinimumLevel&quot;: {\n    &quot;Default&quot;: &quot;Information&quot;,\n    &quot;Override&quot;: {\n      &quot;Microsoft&quot;: &quot;Warning&quot;,\n      &quot;System&quot;: &quot;Warning&quot;\n    }\n  },\n  &quot;WriteTo&quot;: [\n    { &quot;Name&quot;: &quot;Console&quot; },\n    {\n      &quot;Name&quot;: &quot;File&quot;,\n      &quot;Args&quot;: {\n        &quot;path&quot;: &quot;/logs/log-.txt&quot;,\n        &quot;rollingInterval&quot;: &quot;Day&quot;,\n        &quot;rollOnFileSizeLimit&quot;: true,\n        &quot;formatter&quot;: &quot;Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact&quot;\n      }\n    }\n  ],\n  &quot;Enrich&quot;: [ &quot;FromLogContext&quot;, &quot;WithMachineName&quot;, &quot;WithThreadId&quot; ]\n}\n</code></pre>\n<p>You can get a more detailed overview of what's supported with the <code>Serilog.Configuration</code> library in the\n<a href=\"https://github.com/serilog/serilog-settings-configuration\">documentation</a>.</p>\n<h2>Using Serilog In ASP.NET Core</h2>\n<p>We managed to successfully install and configure <strong>Serilog</strong>.\nBut how do we actually use it?</p>\n<p><strong>Serilog</strong> integrates with the <code>ILogger</code> interaface coming from the <code>Microsoft.Extensions.Logging</code> namespace.\nIf you're already using <code>ILogger</code> for logging, everything will continue working correctly.</p>\n<p>Here's a simple example of logging inside of a <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal API</strong></a> endpoint:</p>\n<pre><code class=\"language-csharp\">app.MapGet(&quot;/serilog-is-cool&quot;, (ILogger logger) =&gt;\n{\n    logger.LogInformation(&quot;This is a log inside of the Minimal API endpoint.&quot;);\n\n    return Results.Ok(new { Message = &quot;success&quot; });\n});\n</code></pre>\n<p>You just inject an <code>ILogger</code> or <code>ILogger&lt;T&gt;</code> instance and Serilog will provide its own implementation at runtime.</p>\n<h2>Structured Logging Syntax</h2>\n<p>The idea behind <strong>structured logging</strong> is that you can introduce additional contextual information inside\nof your logs.\n<strong>Serilog</strong> does this using a message template syntax, where you can specify named parameters and then\npass in their values separately.</p>\n<p>Here's an example of what this message template would look like.\nYou specify parameters inside of curly bracers and provide a name, for example <code>{NamedParameter}</code>.\nThe value provided for the parameter will be serialized as a property inside of the corresponding\nstructured log.</p>\n<pre><code class=\"language-csharp\">var book = new { Author = &quot;Domain-Driven Design&quot;, Title = &quot;Eric Evans&quot; };\nvar orderNumber = 1;\n\nlog.LogInformation(\n    &quot;Processing book {@Book}, order number = {@OrderNumber}&quot;,\n    book,\n    orderNumber);\n</code></pre>\n<p>There are a few things to unpack here:</p>\n<ul>\n<li><code>{@Book}</code> parameter which accepts an object</li>\n<li><code>{OrderNumber}</code> parameter which accepts a scalar value</li>\n</ul>\n<p>The <code>@</code> operator in front of <code>Book</code> tells Serilog to serialize the object passed in, instead of converting\nit using <code>ToString()</code>.</p>\n<h2>Benefits Of Structured Logging</h2>\n<p>Lastly, I want to highlight what are some of the benefits of <strong>structured logging</strong> and why you should be using it.</p>\n<p>As I said at the beginning, the main idea with <strong>structured logging</strong> is that all log message follow the same\nstructure.\nThis structure can be a JSON document for example, or a row in a relational table.\nSince structured logs are in a machine-readable format, it's much easier to search through them for\nspecific information.</p>\n<p>When an error occurs, structured logs can provide more context and details about the error, making it easier to\nidentify the root cause and fix the problem.</p>\n<p>It's very easy to start doing structured logging with <strong>Serilog</strong>, and I hope you'll give it a try.</p>\n<p>See you next week, and have an excellent Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/structured-logging-in-asp-net-core-with-serilog",
            "title": "Structured Logging In ASP.NET Core With Serilog",
            "summary": "Serilog is a popular .NET logging library, and what makes it unique is structured logging out of the box.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_025.png",
            "date_modified": "2023-02-18T00:00:00.000Z",
            "date_published": "2023-02-18T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus",
            "content_html": "<p>Azure Service Bus is a messaging service in Azure that lets services communicate without calling each other directly.\nIn .NET you talk to it with the <code>Azure.Messaging.ServiceBus</code> package: <code>ServiceBusSender</code> publishes a message to a queue, and <code>ServiceBusProcessor</code> consumes messages with <code>ProcessMessageAsync</code> and <code>ProcessErrorAsync</code> handlers.</p>\n<p>If you're working in a <strong>distributed system</strong>, you need to be able to communicate between\nmultiple services.\nThere are a few ways that you can implement this.\nDepending on your chosen approach, you can either introduce tight coupling between your\nservices or stay <strong>loosely coupled</strong>.</p>\n<p><strong>Loose coupling</strong> is an important quality in distributed systems.\nIt allows you to evolve your services independently.\nSo how do you implement loosely coupled <strong>communication between services</strong>?</p>\n<p>You need a <strong>messaging system</strong>.</p>\n<p>And <a href=\"https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview\">Azure Service Bus</a>\nis an excellent choice.</p>\n<p>In this week's newsletter, I'll show you how to create an <strong>Azure Service Bus</strong> instance,\nand how to implement messaging over a <strong>queue</strong>.</p>\n<p>Let's dive in.</p>\n<h2>Creating An Azure Service Bus Instance</h2>\n<p>You can create a new <strong>Azure Service Bus</strong> instance from the <strong>Azure</strong> portal.</p>\n<p>I won't go into detail on that, since I find the <strong>Azure</strong> UI pretty intuitive.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_024/service_bus.png\" alt=\"Azure portal steps to create a Service Bus resource from the Integration services category\">\n<p>After creating your <strong>Azure Service Bus</strong> instance, you'll need to do two more things:</p>\n<ul>\n<li>Create a new <strong>queue</strong></li>\n<li>Find the <strong>connection string</strong></li>\n</ul>\n<p>After that you can proceed with implementing the pub-sub pattern over a queue.\nAnd you'll use the connection string from the Azure portal for connecting to the Azure Service Bus instance.</p>\n<h2>Publishing Messages To The Azure Service Bus Queue</h2>\n<p>The first thing we need to do is to create the publishing side of our system, and then\nwe'll see how we can process messages.\nWe're going to use the <code>Azure.Messaging.ServiceBus</code> library to connect to the queue\nrunning in Azure Service Bus.</p>\n<p>You can install the NuGet package by running the following command:</p>\n<pre><code class=\"language-powershell\">Install-Package Azure.Messaging.ServiceBus\n</code></pre>\n<p>And now, let's write the code for publishing a message to an Azure Service Bus queue.</p>\n<p>To work with the Azure Service Bus instance, you will use the <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusclient?view=azure-dotnet\"><code>ServiceBusClient</code></a>\nclass.\nIt requires a connection string to be able to connect to the Azure Service Bus instance.</p>\n<p>The <code>ServiceBusClient</code> is safe to cache and reuse in the application, so it can be registered\nas a service with the singleton lifetime.</p>\n<p>With the <code>ServiceBusClient</code> you can create a <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebussender?view=azure-dotnet\"><code>ServiceBusSender</code></a>\ninstance, which is responsible\nfor sending the actual messages.\nYou also need to specify which queue it will be sending messages to.\nThis can also be the name of a topic, if you are publishing to a topic instead.</p>\n<pre><code class=\"language-csharp\">using Azure.Messaging.ServiceBus;\n\nawait using var client = new ServiceBusClient(ConnectionString);\n\nawait using ServiceBusSender sender = client.CreateSender(QueueName);\n\n// This will be the payload for the message ✉️\nvar productCreated = new ProductCreatedEvent(\n    eventId: Guid.NewGuid(),\n    product.Id,\n    product.Name);\n\nstring json = JsonSerializer.Serialize(productCreated);\n\nvar message = new ServiceBusMessage(json);\n\nawait sender.SendMessageAsync(message);\n</code></pre>\n<p>As you can see, publishing a message to the queue is relatively simple.</p>\n<p>We're creating a new instance of <code>ProductCreatedEvent</code>, which represents our message payload.\nThe payload itself is serialized into a <code>JSON</code> string, wrapped inside a <code>ServiceBusMessage</code>.</p>\n<p>For publishing messages to Azure Service Bus, you simply call the <code>SendMessageAsync</code> method.</p>\n<h2>Receiving Messages From The Azure Service Bus Queue</h2>\n<p>Publishing messages to a queue is only half of the job.\nYou also need to be able to receive messages from the queue.\nHowever, you'll see that this is very similar to the publishing side.</p>\n<p>On the receiving side, you'll also need to create a <code>ServiceBusClient</code>.\nAnd then use it to create an instance of <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusprocessor?view=azure-dotnet\"><code>ServiceBusProcessor</code></a>,\nwhich is used for consuming messages.\nYou need to tell the <code>ServiceBusProcessor</code> which queue it will subscribe to.</p>\n<p>The <code>ServiceBusProcessor</code> exposes two events, which represent callbacks for when a message is received.\nThese events are <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusprocessor.processmessageasync?view=azure-dotnet\"><code>ProcessMessageAsync</code></a>\nand <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusprocessor.processerrorasync?view=azure-dotnet\"><code>ProcessErrorAsync</code></a>.</p>\n<p>You need to provide a handler for these two events, to properly consume messages from the queue.\nIn the example below, we're using the <code>HandleMessageAsync</code> and <code>HandleErrorAsync</code> local functions.</p>\n<pre><code class=\"language-csharp\">using Azure.Messaging.ServiceBus;\n\nawait using var client = new ServiceBusClient(ConnectionString);\n\nawait using ServiceBusProcessor processor  = client.CreateProcessor(QueueName);\n\nprocessor.ProcessMessageAsync += HandleMessageAsync;\n\nprocessor.ProcessErrorAsync += HandleErrorAsync;\n\nawait processor.StartProcessingAsync();\n\nasync Task HandleMessageAsync(ProcessMessageEventArgs args)\n{\n    string json = args.Message.Body.ToString();\n\n    var productCreated = JsonSerializer.Deserialize&lt;ProductCreatedEvent&gt;(json);\n\n    Console.WriteLine(productCreated);\n\n    await args.CompleteMessageAsync(args.Message);\n}\n\nTask HandleErrorAsync(ProcessErrorEventArgs args)\n{\n    var exception = args.Exception;\n\n    Console.WriteLine(exception.ToString());\n\n    return Task.CompletedTask;\n}\n</code></pre>\n<p>The <code>ServiceBusProcessor</code> begins listening to messages coming from the queue after calling the <code>StartProcessingAsync</code> method.</p>\n<p>Notice that the success and error event handlers need to accept the\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.processmessageeventargs?view=azure-dotnet\"><code>ProcessMessageEventArgs</code></a>\nand <a href=\"https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.processerroreventargs?view=azure-dotnet\"><code>ProcessErrorEventArgs</code></a>,\nrespectively.</p>\n<p>From the <code>ProcessMessageEventArgs</code> you can access the <code>Message.Body</code> which contains the mesage payload.</p>\n<h2>Further Reading</h2>\n<p><strong>Azure Service Bus</strong> is a very feature rich service cloud.\nI showed you how to work with <strong>queues</strong>, which are great if you only have one publisher and one subscriber.\nHowever, if you need the ability to have multiple subscribers to a single message, you can't achieve\nthis with queues.</p>\n<p>You will have to use <a href=\"https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-queues-topics-subscriptions#topics-and-subscriptions\">topics</a>,\nand I invite you to research this <em>topic</em> further (pun intended).</p>\n<p><strong>Azure Functions</strong> have excellent support for integrating with <strong>Azure Service Bus</strong>.\nYou can define a <code>QueueTrigger</code> that will run your <strong>Azure Function</strong> when you receive a message to an Azure Service Bus <strong>queue</strong>.</p>\n<p>I also released a video showing how to <a href=\"https://youtu.be/CTKWFMZVIWA\">publish and consume messages using RabbitMQ</a>,\nand I think you might enjoy it after reading this newsletter.</p>\n<p>Have an excellent weekend, and stay awesome!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/messaging-made-easy-with-azure-service-bus",
            "title": "Messaging Made Easy With Azure Service Bus",
            "summary": "Loose coupling is an important quality in a distributed system, and you need a messaging system to get it. Azure Service Bus is an excellent choice.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_024.png",
            "date_modified": "2023-02-11T00:00:00.000Z",
            "date_published": "2023-02-11T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core",
            "content_html": "<p>EF Core wraps every <code>SaveChanges</code> call in a transaction, so all the changes in that call are applied together or none of them are.\nWhen one operation needs several <code>SaveChanges</code> calls, start your own transaction with <code>Database.BeginTransaction</code>, then <code>Commit</code> or <code>Rollback</code> it.\nYou can also hand EF Core an existing transaction with <code>UseTransaction</code>.</p>\n<p>Every software engineer working with <strong>SQL databases</strong> needs to know about <strong>transactions</strong>.\nAnd since most of the time the <strong>SQL database</strong> will be abstracted by an ORM like <strong>EF Core</strong>,\nit's important to understand how you can work with <strong>transactions</strong> using the available\nabstractions.</p>\n<p>So today, I'll show you how to work with <strong>transactions</strong> in <strong>EF Core</strong>.</p>\n<p>Here's what we will cover:</p>\n<ul>\n<li>Default transaction behavior</li>\n<li>Creating transactions</li>\n<li>Using existing transactions</li>\n</ul>\n<p>Let's dive in.</p>\n<h2>Default Transaction Behavior</h2>\n<p>What is the <strong>default</strong> EF Core <strong>transaction behavior</strong>?</p>\n<p>By default, all changes made in a single call to <code>SaveChanges</code> are applied in a\ntransaction. If any of the changes fail, the entire transaction is rolled back\nand no changes are applied to the database. Only if all changes are successfully\npersisted to the database, the call to <code>SaveChanges</code> can complete.</p>\n<p>This is a wonderful feature of <strong>SQL databases</strong> and it saves us many headaches.\nWe don't have to think about the databases remaining in an inconsistent state,\nbecause database <strong>transactions</strong> can do the work for us.</p>\n<p>Let's take a look at an example.</p>\n<pre><code class=\"language-csharp\">using var context = new ShoppingContext();\n\ncontext.LineItems.Add(new LineItem\n{\n    ProductId = productId,\n    Quantity = quantity\n});\n\nvar stock = context.Stock.FirstOrDefault(s =&gt; s.ProductId == productId);\n\nstock.Quantity -= quantity;\n\ncontext.SaveChanges();\n</code></pre>\n<p>Because we are adding a <code>LineItem</code>, and in the same scope reducing the <code>Stock</code>\nquantity, the call to <code>SaveChanges</code> will apply both changes inside of a transaction.\nWe can guarantee that the database will remain in a <strong>consistent state</strong>.</p>\n<h2>Creating Transactions With EF Core</h2>\n<p>What if you want to have more control over <strong>transactions</strong> when working with <strong>EF Core</strong>?</p>\n<p>You can manually create a transaction by accessing the <code>Database</code> facade available\non a <code>DbContext</code> instance and calling <code>BeginTransaction</code>.</p>\n<p>Here's an example where we have multiple calls to <code>SaveChanges</code>. In the default\nscenario, both calls would run in their own transaction. This leaves the possibility\nof the second call to <code>SaveChanges</code> failing, and leaving the database in an\ninconsistent state.</p>\n<pre><code class=\"language-csharp\">using var context = new ShoppingContext();\nusing var transaction  = context.Database.BeginTransaction();\n\ntry\n{\n    context.LineItems.Add(new LineItem\n    {\n        ProductId = productId,\n        Quantity = quantity\n    });\n\n    context.SaveChanges();\n\n    var stock = context.Stock.FirstOrDefault(s =&gt; s.ProductId == productId);\n\n    stock.Quantity -= quantity;\n\n    context.SaveChanges();\n\n    // When we commit the changes, they will be applied to the databases.\n    // The transaction will auto-rollback when it is disposed,\n    // if any command fails.\n    transaction.Commit();\n}\ncatch (Exception)\n{\n    transaction.Rollback();\n}\n</code></pre>\n<p>We call <code>BeginTransaction</code> to manually start a new <strong>database transaction</strong>.\nThis will create a new transaction and return it, so that we can <code>Commit</code> the\ntransaction when we want to complete the operation. You also want to add a\n<code>try-catch</code> block around your code, so that you can <code>Rollback</code> the transaction\nif there are any exceptions.</p>\n<h2>Using Existing Transactions With EF Core</h2>\n<p>Creating a transaction using the EF Core <code>DbContext</code> isn't the only option.\nYou can create a <code>SqlTransaction</code> instance and pass it to <strong>EF Core</strong>, so that\nthe changes applied with EF Core can be committed inside the same <strong>transaction</strong>.</p>\n<p>Here's what I mean:</p>\n<pre><code class=\"language-csharp\">using var sqlConnection = new SqlTransaction(connectionString);\nsqlConnection.Open();\n\nusing var transaction = sqlConnection.BeginTransaction();\n\ntry\n{\n    using var context = new ShoppingContext();\n\n    // Tell EF Core to use an existing transaction.\n    context.UseTransaction(transaction);\n\n    context.LineItems.Add(new LineItem\n    {\n        ProductId = productId,\n        Quantity = quantity\n    });\n\n    context.SaveChanges();\n\n    var stock = context.Stock.FirstOrDefault(s =&gt; s.ProductId == productId);\n\n    stock.Quantity -= quantity;\n\n    context.SaveChanges();\n\n    transaction.Commit();\n}\ncatch (Exception)\n{\n    transaction.Rollback();\n}\n</code></pre>\n<h2>In Summary</h2>\n<p><strong>EF Core</strong> has excellent support for <strong>transactions</strong> and it's very easy to work with.</p>\n<p>You have three options available:</p>\n<ul>\n<li>Rely on the default transaction behavior</li>\n<li>Create a new transaction</li>\n<li>Use an existing transaction</li>\n</ul>\n<p>Most of the time, you want to rely on the default behavior and not have to\nthink about it.</p>\n<p>As soon as you need to perform multiple <code>SaveChanges</code> calls, you should manually\ncreate a transaction, and manage the transaction yourself.</p>\n<p>See you next week, and have an excellent Saturday.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/working-with-transactions-in-ef-core",
            "title": "Working With Transactions In EF Core",
            "summary": "Every software engineer working with SQL databases needs to know about transactions. And since the database is usually abstracted by an ORM like EF Core, it's…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_023.png",
            "date_modified": "2023-02-04T00:00:00.000Z",
            "date_published": "2023-02-04T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-implement-api-key-authentication-in-aspnet-core",
            "content_html": "<p>You implement API Key authentication in ASP.NET Core with a <code>ServiceFilterAttribute</code> that resolves an <code>IAuthorizationFilter</code> from the DI container.\nThe filter reads the key from a request header like <code>X-API-Key</code>, validates it, and sets an <code>UnauthorizedResult</code> when the key isn't valid.\nYou can place the attribute on a single endpoint or on the entire controller.</p>\n<p>In this week's newsletter I want to show you how to implement <strong>API Key authentication</strong>\nin <strong>ASP.NET Core</strong>. This authentication approach uses an <strong>API Key</strong> to authenticate the\nclient of an API. You can pass the <strong>API Key</strong> to the API in a few ways, such as through\nthe query string or a request header.</p>\n<p>I will show you how to implement <strong>API Key authentication</strong> where the <strong>API key</strong> is passed\nin a request header. But the implementation would be similar if we were to use any\nother approach.</p>\n<p>When would you want to use <strong>API Key authentication</strong>? This kind of authentication\nmechanism is common in <strong>Server-to-Server (S2S)</strong> communication. When your API serves\nrequest for other server-side applications to consume and integrate with. It's\nless common in client-server communication scenarios.</p>\n<p>Let's see how we can implement <strong>API Key authentication</strong> in ASP.NET Core!</p>\n<h2>Implementing API Key Authentication</h2>\n<p>We will start off by creating an attribute that we can place on endpoints\nwhere we want to apply <strong>API Key authentication</strong>. It won't be any kind of\nattribute, because we will use a <code>ServiceFilterAttribute</code>.</p>\n<p>What a <code>ServiceFilterAttribute</code> allows us to do is specify a type for the\nfilter that will be created for that attribute.\nThis means we can implement our authentication logic in an <code>IAuthorizationFilter</code>.\nWith a <code>ServiceFilterAttribute</code> we also have support for dependency injection\nin our <code>IAuthorizationFilter</code> implementation.</p>\n<p>Let's first define the <code>ApiKeyAttribute</code> class:</p>\n<pre><code class=\"language-csharp\">public class ApiKeyAttribute : ServiceFilterAttribute\n{\n    public ApiKeyAttribute()\n        : base(typeof(ApiKeyAuthorizationFilter))\n    {\n    }\n}\n</code></pre>\n<p>In the <code>ApiKeyAttribute</code> we specify <code>ApiKeyAuthorizationFilter</code> class as the\nfilter that will be resolved from the DI container. Here's what it looks like:</p>\n<pre><code class=\"language-csharp\">public class ApiKeyAuthorizationFilter : IAuthorizationFilter\n{\n    private const string ApiKeyHeaderName = &quot;X-API-Key&quot;;\n\n    private readonly IApiKeyValidator _apiKeyValidator;\n\n    public ApiKeyAuthorizationFilter(IApiKeyValidator apiKeyValidator)\n    {\n        _apiKeyValidator = apiKeyValidator;\n    }\n\n    public void OnAuthorization(AuthorizationFilterContext context)\n    {\n        string apiKey = context.HttpContext.Request.Headers[ApiKeyHeaderName];\n\n        if (!_apiKeyValidator.IsValid(apiKey))\n        {\n            context.Result = new UnauthorizedResult();\n        }\n    }\n}\n</code></pre>\n<p>The implementation comes down to validating the <strong>API Key</strong> obtained from\nthe header of the current request. If we determine that the <strong>API Key</strong>\nis not valid, we set the value of <code>AuthorizationFilterContext.Result</code>\nto a new instance of an <code>UnauthorizedResult</code>.</p>\n<p>And lastly, all that's left for us to do is implement our custom\nvalidation logic for the <strong>API Key</strong> inside of <code>ApiKeyValidator</code>:</p>\n<pre><code class=\"language-csharp\">public class ApiKeyValidator : IApiKeyValidator\n{\n    public bool IsValid(string apiKey)\n    {\n        // Implement logic for validating the API key.\n    }\n}\n\npublic interface IApiKeyValidator\n{\n    bool IsValid(string apiKey);\n}\n</code></pre>\n<p>The actual implementation for validating the <strong>API Key</strong> will vary based\non your use case, and where you are storing the API keys.\nFor example, if you store the API keys in the database you would check\nif the provided <strong>API Key</strong> exists in the database.\nIf it exists, then validation passes.\nIf it doesn't exist, then validation fails and we return an\n<code>UnauthorizedResult</code>.</p>\n<h2>Registering Services With Dependency Injection</h2>\n<p>We have to make sure to register our <code>ApiKeyAuthorizationFilter</code> and\n<code>ApiKeyValidator</code> services with the dependency injection container.</p>\n<pre><code class=\"language-csharp\">builder.Services.AddSingleton&lt;ApiKeyAuthorizationFilter&gt;();\n\nbuilder.Services.AddSingleton&lt;IApiKeyValidator, ApiKeyValidator&gt;();\n</code></pre>\n<p>This will register them as singleton services in our application.\nYou can use a different service scope such as <code>Transient</code> or <code>Scoped</code>\nif you need to.</p>\n<h2>Applying API Key Authentication To Endpoints</h2>\n<p>Finally, with our <strong>API Key authentication</strong> in place, we can apply the\n<code>ApiKeyAttribute</code> attribute to our endpoints:</p>\n<pre><code class=\"language-csharp\">public class NewslettersController : ControllerBase\n{\n    [ApiKey]\n    [HttpGet]\n    public IActionResult Get()\n    {\n        // ...\n    }\n}\n</code></pre>\n<p>In this case I'm applying the <code>ApiKeyAttribute</code> to an endpoint, but\nyou can also apply it on the <code>NewslettersController</code> and it will add\nauthentication to all the endpoints for that controller.</p>\n<h2>Next Steps</h2>\n<p>Now that you know how to implement <strong>API Key authentication</strong>, I think you\nshould also learn how to implement <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT authentication</strong></a>. And while you're\nat it, why not throw <strong>authorization</strong> into the mix.</p>\n<p>I made a few videos about <strong>JWT authentication</strong> and <strong>permission authorization</strong>\nthat you should take a look at next:</p>\n<ul>\n<li><a href=\"https://youtu.be/4cFhYUK8wnc\">Token Authentication In ASP.NET Core 7 With JWT</a></li>\n<li><a href=\"https://youtu.be/PlbAuNvR16s\">Introduction To Permission Authorization In ASP.NET Core 7</a></li>\n<li><a href=\"https://youtu.be/v4vXDRJ9_sg\">Managing Permissions With EF Core Migrations</a></li>\n<li><a href=\"https://youtu.be/SZtZuvcMBA0\">Implementing A Custom Authorization Handler In ASP.NET Core</a></li>\n<li><a href=\"https://youtu.be/SUyFPp6BPV0\">Using Custom JWT Claims For Authorization In ASP.NET Core</a></li>\n</ul>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-implement-api-key-authentication-in-aspnet-core",
            "title": "How To Implement API Key Authentication In ASP.NET Core",
            "summary": "In this week's newsletter I want to show you how to implement API Key authentication in ASP.NET Core, with the API key passed in a request header.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_022.png",
            "date_modified": "2023-01-28T00:00:00.000Z",
            "date_published": "2023-01-28T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/csharp-yield-return-statement",
            "content_html": "<p>In this week's newsletter I want to talk about the <code>yield</code> keyword in <strong>C#</strong>.\nI think it's a powerful <strong>C#</strong> feature and I wanted to highlight the benefits.</p>\n<p>The <code>yield</code> keyword tells the compiler that the method in which it appears\nis an <strong>iterator block</strong>. An iterator block, or method, returns an <code>IEnumerable</code>\nas the result. And the <code>yield</code> keyword is used to return the values for the\n<code>IEnumerable</code>.</p>\n<p>An interesting thing aboug <code>IEnumerable</code> is that it is lazily evaluted.\nCalling a method with an iterator block doesn't run any code. It's only\nwhen the <code>IEnumerable</code> is iterated over, or enumerated, that we get\nthe actual values. I'll talk about this more later.</p>\n<p>Let's see how we can start using the <code>yield</code> keyword!</p>\n<h2>How To Use The Yield Keyword</h2>\n<p>The <code>yield</code> keyword on it's own doesn't do anything, you have to combine\nit with the <code>return</code> or <code>break</code> statement:</p>\n<ul>\n<li><code>yield return</code> - provides the next value of the iterator</li>\n<li><code>yield-break</code> - signals the end of iteration</li>\n</ul>\n<p>In every project I worked on, there's a piece of code similar to the\nfollowing. You create a list to hold the results, add elements to the\nlist, and return the list in the end.</p>\n<pre><code class=\"language-csharp\">var engineers = GetSoftwareEngineers();\n\npublic IEnumerable&lt;SoftwareEngineer&gt; GetSoftwareEngineers()\n{\n    var result = new List&lt;SoftwareEngineer&gt;();\n\n    for(var i = 0; i &lt; 10; i++)\n    {\n        result.Add(new SoftwareEngineer\n        {\n            Id = i\n        });\n    }\n\n    return result;\n}\n</code></pre>\n<p>You can simplify the method using the <code>yield return</code> statement, and\ncompletely remove the intermediate list required to hold the results.</p>\n<pre><code class=\"language-csharp\">var engineers = GetSoftwareEngineers();\n\npublic IEnumerable&lt;SoftwareEngineer&gt; GetSoftwareEngineers()\n{\n    for(var i = 0; i &lt; 10; i++)\n    {\n        yield return new SoftwareEngineer\n        {\n            Id = i\n        };\n    }\n}\n</code></pre>\n<p>However, it's important to note these two implementation are fundamentally\ndifferent from each other. In the first example, the entire list is\npopulated and materialized. In the second example, the <code>IEnumerable</code>\nreturned will not be materialized and you have to either iterate over\nit inside a <code>foreach</code> loop or call <code>ToList()</code>.</p>\n<h2>Stopping Iteration With Yield Break</h2>\n<p>You can use the <code>yield break</code> statement to stop iteration and exit\nthe iterator block. Typically you would do this when a certain\ncondition is met, or you only want to return a specific set of values\nfrom the iterator block.</p>\n<p>Here's an example where this would be useful:</p>\n<pre><code class=\"language-csharp\">Console.WriteLine(string.Join(&quot;, &quot;, TakeWhilePositive(new[] { 1, 2, -3, 4 })));\n// Output: 1, 2\n\npublic IEnumerable&lt;int&gt; TakeWhilePositive(IEnumerable&lt;int&gt; numbers)\n{\n    foreach(int num in numbers)\n    {\n        if (num &gt; 0)\n        {\n            yield return num;\n        }\n        else\n        {\n            yield break;\n        }\n    }\n}\n</code></pre>\n<h2>Working With IAsyncEnumerable</h2>\n<p>In <strong>C# 8</strong> we got the <code>IAsyncEnumerable</code> type which allows us to\niterate over a collection asynchronously with the <code>yield</code> statement.</p>\n<p>For example, this can be useful when you want to call a thid-party\nAPI multiple times to fetch some data. A common situation is when\nyou get a list of users from the database, and then have to call\nan external storage service to get profile picture information.</p>\n<p>Without <code>IAsyncEnumerable</code> you would have to do something like this:</p>\n<pre><code class=\"language-csharp\">public async Task&lt;IEnumerable&lt;User&gt;&gt; GetUsersAsync()\n{\n    var users = await GetUsersFromDbAsync();\n\n    foreach(var user in users)\n    {\n        user.ProfileImage = await GetProfileImageAsync(user.Id);\n    }\n\n    return users;\n}\n\n// And you would call the method like this.\nvar users = await GetUsersAsync();\n\nforeach(var user in users)\n{\n    Console.WriteLine(user);\n}\n</code></pre>\n<p>Now, consider this same example with the use of <code>IAsyncEnumerable</code>:</p>\n<pre><code class=\"language-csharp\">public async IAsyncEnumerable&lt;User&gt; GetUsersAsync()\n{\n    var users = await GetUsersFromDbAsync();\n\n    foreach(var user in users)\n    {\n        user.ProfileImage = await GetProfileImageAsync(user.Id);\n\n        yield return user;\n    }\n}\n\n// And you would call the method like this.\nawait foreach(var user in GetUsersAsync())\n{\n    Console.WriteLine(user);\n}\n</code></pre>\n<p>The second implementation will iterate over the users returned from\nthe database when they are yielded by the <code>IAsyncEnumerable</code>.</p>\n<h2>When Should I Use Yield?</h2>\n<p>I've found a few interesting practical applications for the <code>yield</code> keyword.\nOne example is when implementing Domain-Driven Design <strong>value objects</strong>.</p>\n<p>Value objects need to support structural equality. They need to implement\na method that returns all of the equality components. Here's an example of\nthat using the <code>yield return</code> statement:</p>\n<pre><code class=\"language-csharp\">public class Address\n{\n    public string City { get; init; }\n\n    public string Street { get; init; }\n\n    public string Zip { get; init; }\n\n    public string Country { get; init; }\n\n    public IEnumerable&lt;object&gt; GetEqualityComponents()\n    {\n        yield return City;\n        yield return Street;\n        yield return Zip;\n        yield return Country;\n    }\n}\n</code></pre>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/csharp-yield-return-statement",
            "title": "C# Yield Return Statement",
            "summary": "In this week's newsletter I want to talk about the yield keyword in C#. The yield keyword tells the compiler that the method is an iterator block, which…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_021.png",
            "date_modified": "2023-01-21T00:00:00.000Z",
            "date_published": "2023-01-21T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/unleash-ef-core-performance-with-compiled-queries",
            "content_html": "<p>Compiled queries let you compile a frequently executed EF Core query once with <code>EF.CompileQuery</code> and reuse it for the lifetime of the application.\nIn my benchmarks on EF Core 7 and SQL Server 2022, the compiled query was consistently around 10% faster than the standard one.\nUse them sparingly, because they add a lot of complexity.</p>\n<p>In this week's newsletter I want to introduce you to an interesting feature\nin <strong>EF Core</strong> called <strong>Compiled Queries</strong>.</p>\n<p>If you have queries that you execute frequently in your application with a\ndifferent set of parameters, it can be helpful to explicitly compile the\nquery and reuse it throughout the lifetime of your application.</p>\n<p><strong>Compiled Queries</strong> are more performant than standard EF queries, because they\ncan take advantage of some additional optimizations.</p>\n<p>Let me show you how to use <strong>Compiled Queries</strong>, and how much performance\nimprovement to expect!</p>\n<h2>How To Create Compiled Queries</h2>\n<p>Lets define a simple class that will represent out data model that we will\nuse for writing <strong>EF</strong> queries:</p>\n<pre><code class=\"language-csharp\">public class Newsletter\n{\n    public long Id { get; init; }\n\n    public string Title { get; init; }\n\n    public int ReadTimeInMinutes { get; init; }\n}\n</code></pre>\n<p>How would we write a simple query to fetch a <code>Newsletter</code> by the <code>Id</code>?\nI think you can do this in your sleep.</p>\n<pre><code class=\"language-csharp\">using var dbContext = new AppDbContext();\n\nvar newsletter = dbContext.Set&lt;Newsletter&gt;().FirstOrDefault(n =&gt; n.Id == id);\n</code></pre>\n<p>Now, how do we convert this query into a <strong>Complied Query</strong>?</p>\n<p>There are a few steps involved:</p>\n<ul>\n<li>Create a <strong>Compiled Query</strong> by calling <code>EF.CompileQuery</code></li>\n<li>Store the <strong>Compiled Query</strong> in a static field, so that it can be reused</li>\n<li>Execute the database query using the <strong>Compiled Query</strong></li>\n</ul>\n<p>You can define the <strong>Compiled Query</strong> in a static field inside of the <code>AppDbContext</code>.\nAnd then expose a method that will accept an argument, and pass it to the\n<strong>Compiled Query</strong> to invoke it.</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\n\npublic class AppDbContext\n{\n    private static Func&lt;AppDbContext, long, Newsletter?&gt; GetNewsletter =\n        EF.CompileQuery(\n            (dbContext, id) =&gt;\n                dbContext.Set&lt;Newsletter&gt;().FirstOrDefault(n =&gt; n.Id == id));\n\n    public Newsletter? GetNewsletter(long id)\n    {\n        return GetNewsletter(this, id);\n    }\n}\n</code></pre>\n<p>This is how we would call the method which invokes the <strong>Compiled Query</strong>:</p>\n<pre><code class=\"language-csharp\">using var dbContext = new AppDbContext();\n\nvar newsletter = dbContext.GetNewsletter(id);\n</code></pre>\n<p>I ran some benchmarks, with the following setup:</p>\n<ul>\n<li><strong>EF Core 7</strong></li>\n<li><strong>SQL Server 2022</strong></li>\n<li>Table with 10,000 records</li>\n</ul>\n<p>The <strong>Compiled Query</strong> was consistently around <strong>10% faster</strong>.</p>\n<p>I also tried running a no-tracking query by calling <code>AsNoTracking()</code> and\nobserved similar results.</p>\n<h2>Why Are Compiled Queries Faster?</h2>\n<p>So we can conclude that <strong>Compiled Queries</strong> are faster. But why is that?</p>\n<p>Let's examine what happens when we execute an <strong>EF</strong> LINQ query. Before <strong>EF</strong>\ncan convert the query into valid SQL that can be executed in the database,\nit needs to compile the query. The compiled query is cached and <strong>EF</strong> will be\nable to reuse that cached query. In some situations the query needs to be\nrecompiled, introducing additional performance costs.</p>\n<p>When we explicitly compile the query by calling <code>EF.CompileQuery</code>, we\ncan utilize some optimization techniques that aren't available at runtime.</p>\n<p>Note that <strong>Compiled Queries</strong> only improve the performance of the in-memory\nportion of executing an EF query. The round trip time and materializing\nresults from the database remain unaffected.</p>\n<h2>Can We Make Compiled Queries Asynchronous?</h2>\n<p>I showed you how to write a synchronous <strong>Compiled Query</strong>. But due to\nperformance considerations we almost always want to execute database\nqueries asynchronously.</p>\n<p>Here's how we can create an asynchronous <strong>Compiled Query</strong>:</p>\n<pre><code class=\"language-csharp\">using Microsoft.EntityFrameworkCore;\n\npublic class AppDbContext\n{\n    private static Func&lt;AppDbContext, string, Task&lt;Newsletter?&gt;&gt; GetByTitle =\n        EF.CompileAsyncQuery(\n            (AppDbContext context, string title) =&gt;\n                context.Set&lt;Newsletter&gt;().FirstOrDefault(c =&gt; c.Title == title));\n\n    public async Task&lt;Newsletter?&gt; GetNewsletterByTitleAsync(string title)\n    {\n        return await GetByTitle(this, title);\n    }\n}\n</code></pre>\n<p>It's interesting that we aren't writing an asynchronous query in the expression\npassed to <code>EF.CompileAsyncQuery</code>. It will be converted to an asynchronous query\nduring compilation.</p>\n<h2>Compiled Queries Aren't a Silver Bullet</h2>\n<p>You might be tempted to go and convert all of your <strong>EF</strong> queries into\n<strong>Compiled Queries</strong>, to squeeze out that last little bit of performance.\nI urge you not do it. <strong>Compiled Queries</strong> are a useful tool, but they\naren't the solution to all your problems.</p>\n<p>Instead, I think we should use <strong>Compiled Queries</strong> sparingly, only in\nsituations where we really need to do these kinds of micro-optimizations.</p>\n<p>Although <strong>Compiled Queries</strong> seem great, we can't deny they increase\nthe complexity of our code considerably. If you think the slight\nperformance improvement gained from using <strong>Compiled Queries</strong> justifies\nthe increase in complexity, then by all means, you should use them.\nOtherwise, I would look for <a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>other ways to improve performance</strong></a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/unleash-ef-core-performance-with-compiled-queries",
            "title": "Unleash EF Core Performance With Compiled Queries",
            "summary": "In this week's newsletter I want to introduce you to an interesting EF Core feature called Compiled Queries.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_020.png",
            "date_modified": "2023-01-14T00:00:00.000Z",
            "date_published": "2023-01-14T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/adding-validation-to-the-options-pattern-in-asp-net-core",
            "content_html": "<p>You add validation to the Options pattern with data annotation attributes like <code>[Required]</code> on the settings class, registered with <code>AddOptions</code>, <code>BindConfiguration</code>, and <code>ValidateDataAnnotations</code>.\nAdd <code>ValidateOnStart</code> and the validation runs when the application starts, instead of the first time you inject <code>IOptions</code>.</p>\n<p>In this week's newsletter I will show you how to easily add <strong>validation</strong>\nto the strongly typed configuration objects injected with <code>IOptions</code>.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7\"><strong>Options pattern</strong></a> allows us to use classes to provide strongly typed\nconfiguration values in our application at runtime.</p>\n<p>But you have no guarantee that the configuration values injected with\n<code>IOptions</code> will be correctly read from the application settings.</p>\n<p>Let's see how we can introduce validation for <code>IOptions</code>, and make sure the application settings are correct.</p>\n<h2>Strongly Typed Configuration</h2>\n<p>I first want to define a simple class that will represent our strongly\ntyped configuration. Let's say we want to integrate with the <strong>GitHub API</strong>,\nso we create a <code>GitHubSettings</code> class to hold our configuration:</p>\n<pre><code class=\"language-csharp\">public class GitHubSettings\n{\n    public string AccessToken { get; init; }\n\n    public string RepositoryName { get; init; }\n}\n</code></pre>\n<p>Inside of our <code>appsettings.json</code> file we need to create a section to\nhold our configuration values:</p>\n<pre><code class=\"language-json\">&quot;GitHubSettings&quot;: {\n    &quot;AccessToken&quot;: &quot;access-token-value&quot;,\n    &quot;RepositoryName&quot;: &quot;youtube-projects&quot;\n}\n</code></pre>\n<p>And with this in place, we can configure our <code>GitHubSettings</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services.Configure&lt;GitHubSettings&gt;(\n    builder.Configuration.GetSection(&quot;GitHubSettings&quot;));\n</code></pre>\n<p>Finally, our <code>GitHubSettings</code> is properly configured and we can inject\nit with <code>IOptions&lt;GitHubSettings&gt;</code>.</p>\n<h2>What Could Go Wrong?</h2>\n<p>If we leave the implementation like this, we're moving the responsibility\nfor providing the correct configuration values to the developer. I'm not\nsaying we are the problem, but I've forgotten to add application settings\na few times. I'm sure this happened to you also.</p>\n<p>Here are just a few things that can go wrong:</p>\n<ul>\n<li>Passing an incorrect section name to <code>IConfiguration.GetSection</code></li>\n<li>Forgetting to add the settings values in <code>appsettings.json</code></li>\n<li>Typo in a property name in the class or in the configuration</li>\n<li>Unbindale properties without a setter</li>\n<li>Data type mismatch resulting in incompatible values</li>\n</ul>\n<p>Depending on which one of these mistakes is made, the application will\nbehave differently at runtime.</p>\n<p>The best case scenario is that the incorrect application settings cause\na runtime exception, and you realize you have a problem and fix it.</p>\n<p>The worst case scenario, and this happens more often than you may think,\nis that the application silently fails. The application settings aren't\ncorrectly set on the value provided by <code>IOptions</code>, but you don't get a\nruntime exception. The problem may go undetected for some time.</p>\n<p>How do we solve this?</p>\n<h2>Validation For The Options Pattern</h2>\n<p>There is a simple way to introduce <strong>validation</strong> to the settings class\nusing <strong>data annotations</strong>. We just add the validation attributes that\nwe need to the properties of the settings class.</p>\n<p>For example, we can add the <code>Required</code> attribute to the <code>GitHubSettings</code>\nproperties:</p>\n<pre><code class=\"language-csharp\">public class GitHubSettings\n{\n    [Required]\n    public string AccessToken { get; init; }\n\n    [Required]\n    public string RepositoryName { get; init; }\n}\n</code></pre>\n<p>We have to slightly change how we configure the <code>GitHubSettings</code>:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddOptions&lt;GitHubSettings&gt;()\n    .BindConfiguration(&quot;GitHubSettings&quot;)\n    .ValidateDataAnnotations();\n</code></pre>\n<p>A few things to note here:</p>\n<ul>\n<li><code>AddOptions</code> - returns an <code>OptionsBuilder&lt;TOptions&gt;</code> that binds to\nthe <code>GitHubSettings</code> class</li>\n<li><code>BindConfiguration</code> - binds the values from the configuration section</li>\n<li><code>ValidateDataAnnotations</code> - enables <strong>validation</strong> using <strong>data annotations</strong></li>\n</ul>\n<p>With this in place, if we try to inject <code>GitHubSettings</code> with any of\nthe properties missing a value, we will get a runtime exception.</p>\n<p>You can also define a <strong>custom delegate</strong> for the <strong>validation</strong> logic, instead\nof using data annotations:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddOptions&lt;GitHubSettings&gt;()\n    .BindConfiguration(&quot;GitHubSettings&quot;)\n    .Validate(gitHubSettings =&gt;\n    {\n        if (string.IsNullOrEmpty(gitHubSettings.AccessToken))\n        {\n            return false;\n        }\n\n        return true;\n    });\n</code></pre>\n<h2>Running Validation At Application Start</h2>\n<p>It would be great if we could run <strong>validation</strong> on the configuration values\nwhen our application is starting, instead of at runtime.</p>\n<p>We can do that by calling <code>ValidateOnStart</code> method when configuring\nour settings class:</p>\n<pre><code class=\"language-csharp\">builder.Services\n    .AddOptions&lt;GitHubSettings&gt;()\n    .BindConfiguration(&quot;GitHubSettings&quot;)\n    .ValidateDataAnnotations()\n    .ValidateOnStart(); // 👈 the magic happens here\n</code></pre>\n<p>When we start the application, the validation will run on <code>GitHubSettings</code>\nand an exception is thrown if validation fails. The validation exception\nwill look something like this:</p>\n<pre><code class=\"language-yaml\">Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException:\n  DataAnnotation validation failed for 'GitHubSettings' members:\n</code></pre>\n<p>This shortens the feedback loop, and you will know right away that you have\na problem. This is much better than finding out that you have a problem at\nruntime, like in the previous examples.</p>\n<h2>Closing Thoughts</h2>\n<p>The <strong>Options pattern</strong> is very flexible and allows us to use strongly typed\nsettings in ASP.NET Core.</p>\n<p>If you want to see how to implement the <a href=\"https://youtu.be/wxYt0motww0\"><strong>Options pattern</strong></a>,\nI made a <a href=\"https://youtu.be/wxYt0motww0\"><strong>video about it where I go into the details.</strong></a>\nI covered the differences between <code>IOptions</code>, <code>IOptionsSnapshot</code> and <code>IOptionsMonitor</code>.</p>\n<p>And now you know how to use the <code>ValidateOnStart</code> method, which was introduced\nin <strong>.NET 6</strong>, to validate your application settings on app start up. This allows\nyou to learn about configuration issues as soon as possible, instead of at runtime.</p>\n<p>I also made a video showing how to add <a href=\"https://youtu.be/qRruEdjNVNE\">validation to the Option pattern</a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/adding-validation-to-the-options-pattern-in-asp-net-core",
            "title": "Adding Validation To The Options Pattern In ASP.NET Core",
            "summary": "In this week's newsletter I will show you how to add validation to the strongly typed configuration objects injected with IOptions.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_019.png",
            "date_modified": "2023-01-07T00:00:00.000Z",
            "date_published": "2023-01-07T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023",
            "content_html": "<p>In this week's newsletter I will share 5 simple tips on how\nyou can be a better software engineer in 2023.</p>\n<p>I find it a little amusing that the last newsletter of the year\nand is also coming out on the last day of the year.</p>\n<p>Here are 5 tips for being a better software engineer in 2023:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#1-keep-learning-and-acquiring-new-skills\">Keep learning and acquiring new skills</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#2-invest-in-code-quality\">Invest in code quality</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#3-work-on-complex-systems\">Work on complex systems</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#4-be-comfortable-in-the-cloud\">Be comfortable in the cloud</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#5-take-care-of-yourself\">Take care of yourself</a></li>\n</ul>\n<p>I'm confident that you will become a better software engineer if you apply one of these, but I challenge you to work on all of them in the coming year.</p>\n<p>Let's dive in.</p>\n<h2>1. Keep Learning And Acquiring New Skills</h2>\n<p>The field of software engineering is constantly evolving, so it's\nimportant to stay up-to-date with <a href=\"https://youtu.be/dDasAmowFts\"><strong>new technologies</strong></a>\nand best practices.</p>\n<p>We have a new .NET release every year, and it's easy to get lost\nwith the latest news. How I stay up to date is through online courses,\nattending conferences and meetups, or working on <a href=\"https://youtu.be/Ru6_b50wdfo\"><strong>personal projects</strong></a>.</p>\n<p>Personal projects transition best to my day job, and I don't shy away\nfrom exploring new topics. I shared many of my personal projects on <a href=\"https://github.com/m-jovanovic\"><strong>GitHub</strong></a>,\nmaybe you can find some inspiration over there.</p>\n<h2>2. Invest In Code Quality</h2>\n<p>In addition to writing clean and readable code, it's important to also\nconsider the overall quality of your code.</p>\n<p>This includes things like performance, security, and maintainability.\nBy writing high-quality code, you'll be able to build systems that are\nmore reliable, scalable, and easier to maintain over time.</p>\n<p>One way to write clean and quality code is <a href=\"https://youtu.be/0nVT1gM4vPg\"><strong>with the help of static code analysis</strong></a>.</p>\n<p>Investing in code quality will pay dividends in the later stages of any project.</p>\n<h2>3. Work On Complex Systems</h2>\n<p>If you aspire to be senior engineer or software architect, you have to\nwork on complex systems. You need to be in a position to solve the toughest problems.</p>\n<p>What do I consider to be a <a href=\"https://youtu.be/Ru6_b50wdfo\"><strong>complex system</strong></a>?</p>\n<p>That's difficult to say, but here are some rough guidelines:</p>\n<ul>\n<li>Microservices</li>\n<li>Event-driven systems</li>\n<li>High performance systems</li>\n</ul>\n<p>Of course, you don't need a <a href=\"https://youtu.be/Ru6_b50wdfo\"><strong>fancy architecture</strong></a> to work in a complex system.</p>\n<p>If you are working in a business domain with many domain rules,\nI consider that a complex system also.</p>\n<p>You should strive to always be in a position to work on a challenging project,\nthis will help you grow.</p>\n<h2>4. Be Comfortable In The Cloud</h2>\n<p>The cloud is here to stay, and you should be familiar with at least one\nof the major cloud providers:</p>\n<ul>\n<li>Microsoft Azure</li>\n<li>Amazon Web Services</li>\n<li>Google Cloud Platform</li>\n</ul>\n<p>Most of them give you free credits to get started and explore the services they offer.</p>\n<p>I stayed away from <a href=\"https://youtu.be/QP0pi7xe24s\"><strong>cloud development</strong></a> for too long in my career, and now I wish I started sooner.</p>\n<p>You want to be <a href=\"https://youtu.be/QP0pi7xe24s\"><strong>comfortable in the cloud</strong></a> to be a better software engineer.</p>\n<h2>5. Take Care Of Yourself</h2>\n<p>Being a software engineer is mentally and physically demanding, and it's\nimportant to take care of yourself in order to maintain a healthy work-life balance.</p>\n<p>This includes getting enough sleep, eating well, and taking breaks when needed.</p>\n<p>Taking care of yourself can also help you stay focused and productive in your work.</p>\n<p>I like to take short breaks every hour or so, and not think about work for a few minutes.\nThis helps me replenish my energy, and allows me to continue to operate on a high level.</p>\n<h2>What I'm Doing To Make 2023 Amazing</h2>\n<p>What I'm Doing To Make 2023 Amazing\nMaking New Year's resolutions is popular at the start of the year. The problem is most people get too excited about it, but then proceed to not accomplish anything in a few months.</p>\n<p>I've found the simplest rule to always make progress is taking action. I try to make a small step forward every day. And when I look back on the year, I realize I made a lot of progress.</p>\n<p>Again, my 5 tips so you can be a better software engineer in 2023 are:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#1-keep-learning-and-acquiring-new-skills\">Keep learning and acquiring new skills</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#2-invest-in-code-quality\">Invest in code quality</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#3-work-on-complex-systems\">Work on complex systems</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#4-be-comfortable-in-the-cloud\">Be comfortable in the cloud</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023#5-take-care-of-yourself\">Take care of yourself</a></li>\n</ul>\n<p>Where I failed most in 2022 was taking care of myself, and this will be one of my main improvement points.</p>\n<p>I also want to learn many new things, so I can share them with you in this newsletter and on my social media channels.</p>\n<p>I wish you a very happy and prosperous New Year.\nWrite lots of code, squash many bugs,\nand may you have green unit tests year round.</p>\n<p>Stay awesome! 🎁<br>\nMilan</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-be-a-better-software-engineer-in-2023",
            "title": "How To Be a Better Software Engineer In 2023",
            "summary": "In this week's newsletter I will share 5 simple tips on how you can be a better software engineer in 2023.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_018.png",
            "date_modified": "2022-12-31T00:00:00.000Z",
            "date_published": "2022-12-31T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design",
            "content_html": "<p>Clean Architecture organizes a system around an inner circle that holds the business rules, with the Infrastructure and Presentation layers around it.\nThe dependency rule points inward: the outer layers depend on the core, and never the other way around.\nThat separation is what makes the business rules easier to unit test and easier to modify.</p>\n<p>In the world of software development, there are countless approaches\nand methodologies to choose from. It's easy to get swayed with the\nlatest trends, and loose sight of architectural principles that really matter.</p>\n<p>One of the more popular ones is <strong>Clean Architecture</strong>, a design approach\nthat prioritizes maintainability, scalability, flexibility, and productivity.</p>\n<p>In this week's newsletter, we will explore the key benefits of using\n<strong>Clean Architecture</strong> and how it can help your team build better software.</p>\n<p>Let's dive in.</p>\n<h2>What Is Clean Architecture?</h2>\n<p><strong>Clean Architecture</strong>, also known as &quot;<a href=\"https://milanjovanovic.tech/blog/clean-architecture-vs-onion-vs-hexagonal\"><strong>The Onion Architecture</strong></a>,&quot; was first\nintroduced by Robert C. Martin (aka &quot;Uncle Bob&quot;) in his book\n&quot;Clean Architecture: A Craftsman's Guide to Software Structure and Design&quot;.</p>\n<p>At its core, <strong>Clean Architecture</strong> is a way of organizing a software system\nin a way that separates the concerns of the various components,\nmaking it easier to understand and maintain.</p>\n<p>In <strong>Clean Architecture</strong>, the core of the system is the <strong>&quot;inner circle&quot;</strong>,\nwhich contains the business rules and logic.</p>\n<p>Surrounding this <strong>inner circle</strong> are layers of abstraction,\neach one representing a different concern.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_017/clean_architecture.png\" alt=\"Clean Architecture layers with Domain at the center, surrounded by Application, Presentation, and Infrastructure\">\n<p>The typical outer layers are <strong>Infrastructure</strong> and <strong>Presentation</strong> layers.\nThe <strong>Infrastructure</strong> layer handles external concerns such as APIs and databases.\nWhile the <strong>Presentation</strong> layer exposes an interface for clients to interact with\nthe application.</p>\n<p>The key principle of <strong>Clean Architecture</strong> is that the <strong>inner circle</strong>\nshould not depend on the outer layers. Instead, the outer layers should\ndepend on the <strong>inner circle</strong>. This helps to ensure that the <strong>core</strong> of the\nsystem is flexible and easy to modify, without worrying about the impact\non other parts of the system.</p>\n<h2>Benefits Of Using Clean Architecture</h2>\n<p>I want to highlight some of the key benefits of using <strong>Clean Architecture</strong>.</p>\n<h3>Improved Maintainability</h3>\n<p>One of the primary benefits of using <strong>Clean Architecture</strong> is improved <strong>maintainability</strong>.\nBy separating the concerns of the various components and enforcing the <a href=\"https://milanjovanovic.tech/blog/dependency-rule-clean-architecture\"><strong>dependency rule</strong></a>,\nit becomes much easier to understand and modify the code.\nDepending on abstractions allows you to design your business logic in a flexible way,\nwithout having to know the implementation details.</p>\n<h3>Modularity and Separation of Concerns</h3>\n<p><strong>Clean Architecture</strong> helps to create a clear <strong>separation of concerns</strong>\nwithin the codebase. Each layer has a specific purpose and is decoupled\nfrom the others, making it easier to understand and modify individual\ncomponents without affecting the rest of the system. This modularity\nalso makes it easier to reuse components in other projects.</p>\n<h3>Testability</h3>\n<p><strong>Clean Architecture</strong> also makes it <strong>easier to test</strong> and debug the code.\nBecause the inner circle is independent of the outer layers,\nit's easier to write unit tests that focus specifically on the business\nrules. This can help to catch errors early on in the development\nprocess and reduce the overall testing effort.</p>\n<h3>Loose Coupling of Components</h3>\n<p><strong>Clean Architecture</strong> also promotes <strong>loose coupling</strong> between the various\ncomponents of the system. This means that it's easier to swap out\nexternal dependencies or make other modifications without affecting\nthe core business logic. This can be especially useful when it comes\nto upgrading or replacing technology.</p>\n<h3>Increased Flexibility</h3>\n<p>Another key benefit of <strong>Clean Architecture</strong> is increased <strong>flexibility</strong>.\nBy separating the concerns of the various components, it's easier\nto modify and adapt the code to changing requirements. This can be\nespecially useful in fast-paced environments where requirements\nare constantly evolving.</p>\n<h3>Improved Team Productivity</h3>\n<p><strong>Clean Architecture</strong> can help to improve team <strong>productivity</strong>.\nBy establishing clear separation of responsibilities and well-defined\nboundaries, it's easier for team members to understand their roles\nand responsibilities. This can improve communication and collaboration,\nleading to more efficient and effective work.</p>\n<h2>Clean Architecture In The Real World</h2>\n<p>This all sounds nice in theory, but how does <strong>Clean Architecture</strong>\nperform in the real world?</p>\n<p>I have used <strong>Clean Architecture</strong> on roughly 10 projects in the\nlast 5 years, and I've had a lot of success with it. It was\neasy to add new features, and scale the applications when\nnecessary. <strong>Clean Architecture</strong> can easily be broken down\ninto multiple modules or services, if performance is suffering\nand there is a need to scale out.</p>\n<p>One problem with the <strong>Clean Architecture</strong> is that it is <a href=\"https://milanjovanovic.tech/blog/clean-architecture-anti-patterns\"><strong>easy to overengineer</strong></a>.</p>\n<p>Dogmatism is a real issue, as I see many people with strong\nopinions of what <strong>Clean Architecture</strong> should be.\nI've been guilty of this myself in the past.</p>\n<p>Recently, I try to be more <strong>pragmatic</strong> when using <strong>Clean Architecture</strong>.\nI apply what I like, and give myself the flexibility of <strong>&quot;breaking&quot;</strong>\n<strong>Clean Architecture</strong> if I think it will simplify things in the long run.</p>\n<h2>Closing Thoughts</h2>\n<p>By following the principles of <strong>Clean Architecture</strong>, you can create a flexible\nand maintainable codebase that is well-suited to evolving requirements and technology.</p>\n<p>However, it's important to be <strong>pragmatic</strong> with <strong>Clean Architecture</strong> and\nallow yourself to be flexible in the design, in order to simplify things in the long run.</p>\n<p>If you want to see how to apply <strong>Clean Architecture</strong> in practice,\nI have a\n<a href=\"https://youtu.be/tLk4pZZtiDY?list=PLYpjLpq5ZDGstQ5afRz-34o_0dexr1RGa\"><strong>playlist with more than 20 videos on Clean Architecture</strong></a>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design",
            "title": "Clean Architecture And The Benefits Of Structured Software Design",
            "summary": "It's easy to get swayed by the latest trends, and lose sight of the architectural principles that really matter.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_017.png",
            "date_modified": "2022-12-24T00:00:00.000Z",
            "date_published": "2022-12-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/fast-document-database-in-net-with-marten",
            "content_html": "<p>Marten is a .NET library that turns a PostgreSQL database into a document store and an event store, using the <code>JSONB</code> support available since PostgreSQL 9.4.\nYou install the NuGet package, call <code>AddMarten</code> with a connection string, and store objects as JSON documents.\nQueries work with LINQ or plain SQL.</p>\n<p>Did you know you can turn <strong>PostgreSQL</strong> into a fully-fledged <strong>Document database</strong>?</p>\n<p><strong>Marten</strong> is a <strong>.NET</strong> library that allows developers to use the <strong>PostgreSQL</strong>\ndatabase as both a <strong>document database</strong> and a fully-featured <strong>event store</strong>.</p>\n<p>You don't need to install anything else to be able to use <strong>PostgreSQL</strong>\nas a <strong>document database</strong>, outside of the Nuget package. <strong>Marten</strong> relies\non the <strong>JSONB</strong> support available since <strong>PostgreSQL</strong> 9.4.</p>\n<p>In this week's newsletter, I want to introduce you to the basics of working\nwith <strong>Marten</strong> and show you how easy it is to get started.</p>\n<p>Let's dive in.</p>\n<h2>Installing And Configuring Marten</h2>\n<p>What are you going to need to start using <strong>PostgreSQL</strong> as a <strong>Document datbase</strong>?</p>\n<p>Other than a running instance of <strong>PostgreSQL</strong>, of course, you will need\nto install the <strong>Marten</strong> Nuget package:</p>\n<pre><code class=\"language-csharp\">dotnet add package Marten\n</code></pre>\n<p><strong>Marten</strong> can build the required database schema and necessary tables on the fly,\nand I suggest using this approach in development.\nFor a production environment, you definitely want to apply schema\nchanges on your own with migration scripts.</p>\n<p>To register <strong>Marten</strong> with dependency injection, you need to call the <code>AddMarten</code>\nmethod.</p>\n<p>Here's an example <strong>Marten</strong> configuration inside of a <strong>.NET 7</strong> application:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddMarten(options =&gt;\n{\n    options.Connection(builder.Configuration.GetConnectionString(&quot;Marten&quot;));\n});\n</code></pre>\n<p>This will register a few services with dependency injection:</p>\n<ul>\n<li><code>IDocumentStore</code> - used to create sessions, generate schema migrations, and do bulk inserts</li>\n<li><code>IDocumentSession</code> - used for read and write operations</li>\n<li><code>IQuerySession</code> - used for read operations</li>\n</ul>\n<p>Let's see how we can work with documents using <strong>Marten</strong>.</p>\n<h2>Storing Documents With Marten</h2>\n<p>Storing documents in the database is very straightforward. You need to create\na new <code>DocumentStore</code> instance, and open an <code>IDocumentSession</code> which exposes\nmethods for storing and persisting documents.</p>\n<p>Let's see how we can store a <code>Product</code> document:</p>\n<pre><code class=\"language-csharp\">var store = DocumentStore.For(&quot;Connection string to PostgreSQL&quot;);\n\nusing var session = store.OpenSession();\n\nvar product = new Product\n{\n    Name = &quot;C# 11 and .NET 7 - Modern Cross-Platform Fundamentals&quot;,\n    Price = 46.87\n};\n\nsession.Store(product);\n\nawait session.SaveChangesAsync();\n</code></pre>\n<p>We're creating a new <code>DocumentStore</code> instance which we use to open\na session to <strong>PostgreSQL</strong>. And then we just call <code>Store</code> and pass in\nthe <code>Product</code> instance. Note that <strong>Marten</strong> will populate the\n<code>Product.Id</code> at this point. <strong>Marten</strong> can populate keys of <code>Guid</code>,\n<code>int</code>, <code>long</code>, and other data types. It uses the HiLo algorithm\nfor numeric keys. Finally, when we call <code>SaveChangesAsync</code> the\n<code>Product</code> is serialized into <strong>JSON</strong> and persisted as a document.</p>\n<p>An important thing to be aware of is that the <code>IDocumentSession</code>\ncreated by <code>OpenSession</code> doesn't track changes on the entities automatically.\nYou need to create a session with dirty checking enabled by\ncalling <code>DirtyTrackedSession</code> on the <code>DocumentStore</code> to enable\nautomatic change detection.</p>\n<h2>Querying Documents With Marten</h2>\n<p><strong>Marten</strong> has rich support for querying documents in the database.\nYou can write and execute queries using <strong>LINQ</strong>, which you are\nfamiliar with if you worked with <strong>EF Core</strong>. And you can also\nwrite and execute <strong>SQL</strong> queries, because it's still a <strong>PostgreSQL</strong>\ndatabase underneath.</p>\n<p>Here's an example query to return products that have a higher price\nthan the one which is specified:</p>\n<pre><code class=\"language-csharp\">var store = DocumentStore.For(&quot;Connection string to PostgreSQL&quot;);\n\nusing var session = store.QuerySession();\n\nvar products = session.Query&lt;Product&gt;().Where(p =&gt; p.Price &gt; 9.99).ToList();\n</code></pre>\n<p><strong>Marten</strong> also has support for:</p>\n<ul>\n<li>Including related documents</li>\n<li>Batched queries</li>\n<li>Paging</li>\n<li>Full text search</li>\n</ul>\n<h2>Advanced Options With Marten</h2>\n<p><strong>Marten</strong> can utilize the full capabilities <strong>PostgreSQL</strong> has to offer,\nnotably transactions and indexing. <strong>Marten</strong> sessions are transactional\nby default, either all of the documents are persisted together or\nnone of them are. And you can configure indexes on your documents\nfor faster queries.</p>\n<p><strong>Marten</strong> isn't just a <strong>document database</strong> on top of <strong>PostgreSQL</strong>!</p>\n<p>You have fully-fledged support for <a href=\"https://milanjovanovic.tech/blog/introduction-to-event-sourcing-for-net-developers\"><strong>event sourcing</strong></a> with <strong>Marten</strong>,\nas well as projections. This makes it a perfect choice for\nimplementing <strong>CQRS</strong>. But this is a topic for a separate newsletter.</p>\n<h2>Closing Thoughts</h2>\n<p>I'm absolutely amazed with <a href=\"https://martendb.io/\">Marten</a> and what it has to offer.\nAnd <strong>PostgreSQL</strong> is also my favorite database, so it's like a match\nmade in heaven. I don't get too excited about learning new\ntechnologies, but <strong>Marten</strong> has been an endless source\nof joy this past week.</p>\n<p>I still need to explore a few more topics before I can consider it\nfor production use:</p>\n<ul>\n<li>Schema migrations</li>\n<li>Relationships and foreign keys</li>\n<li>Advanced configuration options</li>\n</ul>\n<p>Considering that <strong>PostgreSQL</strong> is cheaper than most <strong>document databases</strong>,\nI think using <strong>Marten</strong> is an interesting alternative. And if you are\nfamiliar with <strong>SQL</strong> databases, you can still use all of that knowledge.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/fast-document-database-in-net-with-marten",
            "title": "Fast Document Database In .NET With Marten",
            "summary": "Did you know you can turn PostgreSQL into a fully-fledged Document database? Marten is a .NET library that lets you use PostgreSQL as both a document database…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_016.png",
            "date_modified": "2022-12-17T00:00:00.000Z",
            "date_published": "2022-12-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-structure-minimal-apis",
            "content_html": "<p>To keep <strong>Minimal APIs</strong> maintainable, group endpoints by feature instead of defining them all in one file.\nYou can write an extension method on <code>IEndpointRouteBuilder</code> for each feature, or use a library like <strong>Carter</strong> and its module concept.\nEither way, related endpoints stay together as the API grows in complexity.</p>\n<p>In this week's newsletter we are going to explore <a href=\"https://milanjovanovic.tech/blog/minimal-apis-dotnet\"><strong>Minimal APIs</strong></a>,\nwhich were introduced in <strong>.NET 6</strong>.</p>\n<p><strong>Minimal APIs</strong> were introduced to remove some of the ceremony\nof creating <strong>traditional APIs with controllers</strong>.\nTo define an endpoint, you can use the new extension\nmethods, such as <code>MapGet</code> to define a <strong>GET</strong> endpoint.</p>\n<p>I see one big issue with <strong>Minimal APIs</strong>, and that is the lack\nof clear guidance around how to structure applications\nbuilt with <strong>Minimal APIs</strong>.</p>\n<p>In this newsletter, I want to offer a few solutions for that problem.</p>\n<p>Let's dive in.</p>\n<h2>How To Create Minimal APIs?</h2>\n<p>Let's define a simple <strong>Minimal API</strong> application with two endpoints.\nWe're going to create one <code>GET</code> endpoint for getting a list of products.\nAnd one <code>POST</code> endpoint for saving a product to the database.</p>\n<p>We're using the powerful <strong>DI</strong> feature that allows us to inject services\nas expression arguments, which you can see in the two expressions below\nwhere we are injecting the <code>AppDbContext</code>.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\n// Configure EF and other services...\n\nvar app = builder.Build();\n\napp.MapGet(&quot;/products&quot;, async (AppDbContext dbContext) =&gt;\n{\n    return Results.Ok(await dbContext.Products.ToListAsync());\n});\n\napp.MapPost(&quot;/products&quot;, async (Product product, AppDbContext dbContext) =&gt;\n{\n    dbContext.Products.Add(product);\n\n    await dbContext.SaveChangesAsync();\n\n    return Results.Ok(product);\n});\n\napp.Run();\n</code></pre>\n<p>And with this we have a functioning <strong>Minimal API</strong> that we can develop further\nas we continue to add more endpoints.</p>\n<h2>The Problem With Maintaining Minimal APIs</h2>\n<p>There is one potential problem with structuring our <strong>Minimal APIs</strong> like\nin the previous example. If we keep adding the <strong>Minimal API</strong> endpoints\nin the same file, our API will become hard to maintain as it grows\nin complexity. How can we solve the maintance problem with <strong>Minimal APIs</strong>?</p>\n<p>One solution can be to use extension methods to encapsulate the\ndefiniton of the <strong>Minimal API</strong> endpoints.</p>\n<p>Here's an example of that:</p>\n<pre><code class=\"language-csharp\">public static class ProductsModule\n{\n    public static void RegisterProductsEndpoints(this IEndpointRouteBuilder  endpoints)\n    {\n        endpoints.MapGet(&quot;/products&quot;, async (AppDbContext dbContext) =&gt;\n        {\n            return Results.Ok(await dbContext.Products.ToListAsync());\n        });\n\n        endpoints.MapPost(&quot;/products&quot;, async (Product product, AppDbContext dbContext) =&gt;\n        {\n            dbContext.Products.Add(product);\n\n            await dbContext.SaveChangesAsync();\n\n            return Results.Ok(product);\n        });\n    }\n}\n</code></pre>\n<p>And then inside of <code>Program</code> we need to register the endpoints:</p>\n<pre><code class=\"language-csharp\">app.RegisterProductsEndpoints();\n</code></pre>\n<p>You can see that this simplifies our <strong>Minimal API</strong> definition,\nand we also have our endpoints grouped by feature in one place.\nI think this improve the maintainability of <strong>Minimal APIs</strong>,\nbut it comes at a cost. And that cost is having to define\nextension methods for each group of endpoints you want to encapsulate,\nand then you have to remember to call that extensions method in <code>Program</code>.</p>\n<p>Can we do better?</p>\n<h2>Structuring Minimal API Projects With Modules</h2>\n<p>I want to introduce you to an interesting open source library\ncalled <a href=\"https://github.com/CarterCommunity/Carter\">Carter</a>,\nwhich has a concept of modules that we can use to group endpoints.</p>\n<p>Here's how we can define our <code>ProductsModule</code> with <strong>Carter</strong>:</p>\n<pre><code class=\"language-csharp\">public class ProductsModule : ICarterModule\n{\n    public void AddRoutes(IEndpointRouteBuilder app)\n    {\n        app.MapGet(&quot;/products&quot;, async (AppDbContext dbContext) =&gt;\n        {\n            return Results.Ok(await dbContext.Products.ToListAsync());\n        });\n\n        app.MapPost(&quot;/products&quot;, async (Product product, AppDbContext dbContext) =&gt;\n        {\n            dbContext.Products.Add(product);\n\n            await dbContext.SaveChangesAsync();\n\n            return Results.Ok(product);\n        });\n    }\n}\n</code></pre>\n<p>This takes care of configuring our <strong>Minimal API</strong> endpoints, but we still\nneed to tell the framework to use these endpoints. We have to slightly\nmodify the <code>Program</code> to register the required <strong>Carter</strong> services by\ncalling <code>AddCarter</code>, and also map our endpoints by calling <code>MapCarter</code>.</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\n\n// Configure EF and other services...\n\nbuilder.Services.AddCarter();\n\nvar app = builder.Build();\n\napp.MapCarter();\n\napp.Run();\n</code></pre>\n<p>When we want to define additional <strong>Minimal API</strong> endpoints we just need to\nimplement a new <code>ICarterModule</code>, and register our endpoints. <strong>Carter</strong> will\nautomatically take care of registering the new endpoints after that.</p>\n<h2>Would I Use Minimal APIs In a Real Project?</h2>\n<p>I think <strong>Minimal APIs</strong> have evolved nicely since they were first introduced\nin <strong>.NET 6</strong>. I would be careful with using them in very large applications,\nbut I'm definitely going to explore options for using them on smaller projects.</p>\n<p>A good use case can be for building a <a href=\"https://milanjovanovic.tech/blog/microservices-dotnet-getting-started\"><strong>microservice</strong></a> that has a limited\nnumber of endpoints.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-structure-minimal-apis",
            "title": "How To Structure Minimal APIs",
            "summary": "Minimal APIs were introduced to remove some of the ceremony of creating traditional APIs with controllers.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_015.png",
            "date_modified": "2022-12-10T00:00:00.000Z",
            "date_published": "2022-12-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core",
            "content_html": "<p>To run a <strong>background task</strong> in ASP.NET Core, implement the <code>IHostedService</code> interface, or inherit from the <code>BackgroundService</code> class and override <code>ExecuteAsync</code>.\nRegister the task with <code>builder.Services.AddHostedService</code>, and it runs as a singleton service alongside your application.\nFor repeating work, use a <code>PeriodicTimer</code> inside <code>ExecuteAsync</code> to run the task on a fixed period.</p>\n<p>In this week's newsletter we will talk about running <strong>background tasks</strong> in <strong>ASP.NET Core</strong>.\nAfter reading this newsletter, you will be able to set up a <strong>background task</strong>\nand have it up and running within minutes.</p>\n<p><strong>Background tasks</strong> are used to offload some work in your application to the background,\noutside of the normal application flow. A typical example can be asynchronously\nprocessing messages from a queue.</p>\n<p>I will show you how to create a simple <strong>background task</strong> that runs once and completes.</p>\n<p>And you will also see how to configure a continuous <strong>background task</strong>, that repeats after a specific period.</p>\n<p>Let's dive in.</p>\n<h2>Background Tasks With IHostedService</h2>\n<p>You can define a <strong>background task</strong> by implementing the <code>IHostedService</code> interface.\nIt has only two methods.</p>\n<p>Here's what the <code>IHostedService</code> interface looks like:</p>\n<pre><code class=\"language-csharp\">public interface IHostedService\n{\n    Task StartAsync(CancellationToken cancellationToken);\n\n    Task StopAsync(CancellationToken cancellationToken);\n}\n</code></pre>\n<p>All you have to do is implement the <code>StartAsync</code> and <code>StopAsync</code> methods.</p>\n<p>Inside of <code>StartAsync</code> you would usually perform the background processing.\nAnd inside of <code>StopAsync</code> you would perform any cleanup that is necessary,\nsuch as disposing of resources.</p>\n<p>To configure the <strong>background task</strong> you have to call the <code>AddHostedService</code> method:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddHostedService&lt;MyBackgroundTask&gt;();\n</code></pre>\n<p>Calling <code>AddHostedService</code> will configure the <strong>background task</strong>\nas a <strong>singleton</strong> service.</p>\n<p>So does dependency injection still work in <code>IHostedService</code> implementations?<br>\nYes, but you can only inject <strong>transient</strong> or <strong>singleton</strong> services.</p>\n<p>However, I don't like to implement the <code>IHostedService</code> interface myself.\nI prefer using the <code>BackgroundService</code> class instead.</p>\n<h2>Background Tasks With BackgroundService</h2>\n<p>The <code>BackgroundService</code> class already implements the <code>IHostedService</code> interface,\nand it has an <code>abstract</code> method that you need to override - <code>ExecuteAsync</code>.\nWhen you are using the <code>BackgroundService</code> class, you only have to think about\nthe operation you want to implement.</p>\n<p>Here's an example <strong>background task</strong> that runs <a href=\"https://milanjovanovic.tech/blog/ef-core-migrations-best-practices\"><strong>EF migrations</strong></a>:</p>\n<pre><code class=\"language-csharp\">public class RunEfMigrationsBackgroundTask : BackgroundService\n{\n    private readonly IServiceProvider _serviceProvider;\n\n    public RunEfMigrationsBackgroundTask(IServiceProvider serviceProvider)\n    {\n        _serviceProvider = serviceProvider;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        using IServiceScope scope = _serviceProvider.CreateScope();\n\n        await using AppDbContext dbContext =\n            scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\n\n        await dbContext.Database.MigrateAsync(stoppingToken);\n    }\n}\n</code></pre>\n<p>The <strong>EF</strong> <code>DbContext</code> is a <strong>scoped</strong> service, which we can't inject directly\ninside of <code>RunEfMigrationsBackgroundTask</code>. We have to inject an instance of\n<code>IServiceProvider</code> which we can use to create a custom service scope,\nso that we can resolve the scoped <code>AppDbContext</code>.</p>\n<p>I would <em>not recommend</em> running the <code>RunEfMigrationsBackgroundTask</code> in production.\n<strong>EF</strong> migrations can easily fail and you'll run into problems.\nHowever, I think it's perfectly fine for local development.</p>\n<h2>Periodic Background Tasks</h2>\n<p>Sometimes we want run a <strong>background task</strong> continuously, and have it\nperform some operation on repeat. For example, we want consume messages\nfrom a queue every ten seconds. How do we build this?</p>\n<p>Here's an example <code>PeriodicBackgroundTask</code> to get you started:</p>\n<pre><code class=\"language-csharp\">public class PeriodicBackgroundTask : BackgroundService\n{\n    private readonly TimeSpan _period = TimeSpan.FromSeconds(5);\n    private readonly ILogger&lt;PeriodicBackgroundTask&gt; _logger;\n\n    public PeriodicBackgroundTask(ILogger&lt;PeriodicBackgroundTask&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        using PeriodicTimer timer = new PeriodicTimer(_period);\n\n        while (!stoppingToken.IsCancellationRequested &amp;&amp;\n               await timer.WaitForNextTickAsync(stoppingToken))\n        {\n            _logger.LogInformation(&quot;Executing PeriodicBackgroundTask&quot;);\n        }\n    }\n}\n</code></pre>\n<p>We're using a <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.periodictimer?view=net-6.0\">PeriodicTimer</a>\nto asynchronously wait for a given period, before executing our <strong>background task</strong>.</p>\n<h2>What If You Need A More Robust Solution?</h2>\n<p>It should be obvious by now that <code>IHostedService</code> is useful when you need\nsimple <strong>background tasks</strong> that are running while your application is running.</p>\n<p>What if you want to have a scheduled <strong>background task</strong> that runs at 2AM every day?</p>\n<p>You can probably build something like this yourself, but there are <strong>existing solutions</strong>\nthat you should consider first.</p>\n<p>Here are two popular solutions for running <strong>background tasks</strong> that I worked with before:</p>\n<ul>\n<li><a href=\"https://www.quartz-scheduler.net/\">Quartz</a></li>\n<li><a href=\"https://www.hangfire.io/\">Hangfire</a></li>\n</ul>\n<p>I also have an example of <a href=\"https://youtu.be/XALvnX7MPeo\">using Quartz for processing Outbox messages</a>\non my YouTube channel that you can take a look at.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/running-background-tasks-in-asp-net-core",
            "title": "Running Background Tasks In ASP.NET Core",
            "summary": "In this week's newsletter we will talk about running background tasks in ASP.NET Core. Background tasks offload work to the background, outside of the normal…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_014.png",
            "date_modified": "2022-12-03T00:00:00.000Z",
            "date_published": "2022-12-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7",
            "content_html": "<p><strong>EF Core 7</strong> added the <code>ExecuteUpdate</code> and <code>ExecuteDelete</code> methods for running bulk updates and deletes.\nYou write a LINQ query that selects the target records, and EF Core translates it into a single <code>UPDATE</code> or <code>DELETE</code> statement that runs directly in the database.\nFrom my testing, that can be as much as 10x faster than loading entities and calling <code>SaveChanges</code>.</p>\n<p>In this week's newsletter, we're going to explore the new\n<code>ExecuteUpdate</code> and <code>ExecuteDelete</code> methods that were released with <strong>EF7</strong>.</p>\n<p><code>ExecuteUpdate</code> allows us to write a query and run a bulk update\noperation on the entities matching that query.</p>\n<p>Similarly, <code>ExecuteDelete</code> allows us to write a query and delete\nthe entities matching that query.</p>\n<p>We can significantly improve performance using the new methods in some scenarios, and I'm going to show you what those scenarios are.</p>\n<p>Let's dive in.</p>\n<h2>Updating And Deleting Entities Before EF Core 7</h2>\n<p>If you want to update a collection of entities before <strong>EF7</strong>,\nyou need to load the entities into memory using the <code>DatabaseContext</code>.</p>\n<p>The <a href=\"https://milanjovanovic.tech/blog/change-tracker-ef-core\"><strong>EF ChangeTracker</strong></a> will then track any changes made to these entities.\nWhen you are ready to commit the changes to the database,\nyou simply call the <code>SaveChanges</code> method.</p>\n<p>Here's an example where we load a few notifications,\nand we want to snooze them so they aren't sent:</p>\n<pre><code class=\"language-csharp\">var notifications = dbContext\n    .Notifications\n    .Where(n =&gt; !n.Snoozed)\n    .ToList();\n\nforeach(var notification in notifications)\n{\n    notification.Snoozed = true;\n}\n\ndbContext.SaveChanges();\n</code></pre>\n<p><strong>EF7</strong> will generate the following <strong>SQL</strong> statement to update the records in the database:</p>\n<pre><code class=\"language-sql\">UPDATE [Notifications] n\nSET n.[Snoozed] = TRUE\nWHERE n.[Id] = @notificationId_1;\n\n...\n\nUPDATE [Notifications] n\nSET n.[Snoozed] = TRUE\nWHERE n.[Id] = @notificationId_N;\n</code></pre>\n<p>Notice that for every notification we end up with one <strong>SQL UPDATE</strong> statement.\nThis won't scale well as the number of notifications increases.</p>\n<h2>Updating Entities With ExecuteUpdate</h2>\n<p>With <strong>EF7</strong>, we now have access to the new <code>ExecuteUdpate</code> method.\nIt also has an asynchronous version - <code>ExecuteUpdateAsync</code>.</p>\n<p>How do you use it?</p>\n<p>You need to write a query that will select the records you want to update,\nand then call the <code>ExecuteUpdate</code> method on the resulting <code>IQueryable</code>.</p>\n<p>Let's rewrite the previous example using the new approach:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Notifications\n    .Where(n =&gt; !n.Snoozed)\n    .ExecuteUpdate(s =&gt; s.SetProperty(\n        n =&gt; n.Snoozed,\n        n =&gt; true));\n</code></pre>\n<p>In the call to <code>ExecuteUpdate</code> we call the <code>SetProperty</code> method to specify\nwhich properties we want to update, and what values we want to set.\nThe <code>SetProperty</code> method can be called multiple times, if you need to update more than one property.</p>\n<p>In this case, <strong>EF7</strong> will generate the following <strong>SQL</strong> query:</p>\n<pre><code class=\"language-sql\">UPDATE n\nSET n.[Snoozed] = TRUE\nFROM [Notifications] AS n\nWHERE n.[Snoozed] = FALSE;\n</code></pre>\n<p>Notice that this time we only have one <strong>SQL</strong> query being sent to the database.\nThis is a major performance improvement. It can be as much as 10x faster\nthan the old version, from my testing.</p>\n<h2>Deleting Entities With ExecuteDelete</h2>\n<p>Let's also see how we can do bulk deletes using the <code>ExecuteDelete</code> and <code>ExecuteDeleteAsync</code> methods.</p>\n<p>Again, you have to write a query that will select the records you want to delete,\nand then call the <code>ExecuteDelete</code> method on the resulting <code>IQueryable</code>.</p>\n<p>If you want to delete all snoozed notifications:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Notifications\n    .Where(n =&gt; n.Snoozed)\n    .ExecuteDelete();\n</code></pre>\n<p>And <strong>EF7</strong> will generate the following <strong>SQL</strong> query:</p>\n<pre><code class=\"language-sql\">DELETE FROM n\nFROM [Notifications] AS n\nWHERE n.[Snoozed] = TRUE;\n</code></pre>\n<p>I think this will be incredibly useful when you want to\ndelete records in the database based on a specific condition.</p>\n<h2>Transactions, Change Tracking And Query Filters With Bulk Methods</h2>\n<p>You need to be aware how transactions and change tracking\nwork with the new bulk methods. <code>ExecuteUpdate</code> and <code>ExecuteDelete</code> will\nimmediately go to database, and run the <strong>SQL</strong> query.</p>\n<p><strong>What does this mean for transactions?</strong></p>\n<p>If you want to run a bulk method together with other updates\napplied with <code>SaveChanges</code>, by default they won't run in the same transaction.\nYou need to open an explicit <strong>transaction</strong> using the <code>DatabaseContext</code> to keep everything consistent.</p>\n<p><strong>What does this mean for change tracking?</strong></p>\n<p><code>ExecuteUpdate</code> and <code>ExecuteDelete</code> run directly on the database, without loading any entities into memory.\n<strong>EF7</strong> will not track these entities in the <code>ChangeTracker</code>.</p>\n<p>If you have any database interceptors defined, they won't execute\nafter calling one of the bulk update methods.\nThis also means that if you override <code>SaveChanges</code>\nto add custom behavior, it won't be called.</p>\n<p><strong>Do Query Filters still work?</strong></p>\n<p>Yes, <strong>query filters</strong> will be <strong>correctly applied</strong> when calling <code>ExecuteUpdate</code> or <code>ExecuteDelete</code>.</p>\n<h2>When Should You Use The New Bulk Methods?</h2>\n<p>I think this is an excellent new addition to <strong>EF7</strong>,\nand it solves a real problem when you need to run\na typical <strong>UPDATE</strong> or <strong>DELETE</strong> query with a\n<strong>WHERE</strong> statement applied.</p>\n<p>Previously, you had to write raw <strong>SQL</strong> and execute it using something like <a href=\"https://milanjovanovic.tech/blog/dapper-dotnet-guide\"><strong>Dapper</strong></a>.</p>\n<p>I will likely use this approach when it applies to my projects.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7",
            "title": "How To Use The New Bulk Update Feature In EF Core 7",
            "summary": "In this week's newsletter, we're going to explore the new ExecuteUpdate and ExecuteDelete methods that were released with EF7.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_013.png",
            "date_modified": "2022-11-26T00:00:00.000Z",
            "date_published": "2022-11-26T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7",
            "content_html": "<p>The <strong>options pattern</strong> uses classes to provide strongly typed settings in your application at runtime.\nYou bind a configuration section to an options class with <code>IServiceCollection.Configure</code>, and consume it through dependency injection with <code>IOptions</code>.\nThe values can come from multiple sources, most commonly application configuration.</p>\n<p>In this week's newsletter I want to show you how you can use\nthe powerful <strong>options pattern</strong> in <strong>ASP.NET Core 7</strong>.</p>\n<p>The <strong>options pattern</strong> uses classes to provide <strong>strongly typed settings</strong>\nin your application at runtime.</p>\n<p>The values for the <strong>options</strong> instance can come from multiple sources.\nThe typical use case is to provide the settings from application configuration.</p>\n<p>You can configure the <strong>options pattern</strong> in a few different ways in <strong>ASP.NET Core</strong>.\nI want to discuss some of the approaches and their potential benefits.</p>\n<p>Let's dive in.</p>\n<h2>Creating The Options Class</h2>\n<p>I want to set the stage first, by creating the <strong>options</strong> class and\nexplaining what settings we want to bind to it.</p>\n<p>We want to configure <a href=\"https://milanjovanovic.tech/blog/jwt-authentication-aspnetcore\"><strong>JWT Authentication</strong></a> for our application,\nso we decided to create the <code>JwtOptions</code> class to hold that configuration:</p>\n<pre><code class=\"language-csharp\">public class JwtOptions\n{\n    public string Issuer { get; init; }\n    public string Audience { get; init; }\n    public string SecretKey { get; init; }\n}\n</code></pre>\n<p>And let's imagine that inside of our <code>appsettings.json</code> file\nwe have the following configuration values:</p>\n<pre><code class=\"language-json\">&quot;Jwt&quot;: {\n    &quot;Issuer&quot;: &quot;Gatherly&quot;,\n    &quot;Audience&quot;: &quot;Gatherly&quot;,\n    &quot;SecretKey&quot;: &quot;dont-tell-anyone!&quot;\n}\n</code></pre>\n<p>Alright, that's looking good. Now I want to show you a few ways to bind\nthe values from JSON to our <code>JwtOptions</code> class.</p>\n<h2>Setting Up Options Pattern Using IConfiguration</h2>\n<p>The most straightforward approach is to use the <code>IConfiguration</code> instance\nthat we can access while registering services.</p>\n<p>We need to call the <code>IServiceCollection.Configure&lt;TOptions&gt;</code> method,\nand specify the <code>JwtOptions</code> as the generic argument:</p>\n<pre><code class=\"language-csharp\">builder.Services.Configure&lt;JwtOptions&gt;(\n    builder.Configuration.GetSection(&quot;Jwt&quot;));\n</code></pre>\n<p>It doesn't get simpler than this, does it?</p>\n<p>The only downside is that we are limited to the configuration\nvalues provided through application configuration.</p>\n<p>This can be extended to include environment variables and user secrets also.</p>\n<h2>Setting Up Options Pattern Using IConfigureOptions</h2>\n<p>If you want a more robust approach, I have you covered.\nWe're going to use the <code>IConfigureOptions</code> interface to define a class\nto configure our <strong>strongly typed options</strong>.</p>\n<p>There are two steps that we need to follow in this case:</p>\n<ul>\n<li>Create the <code>IConfigureOptions</code> implementation</li>\n<li>Call <code>IServiceCollection.ConfigureOptions&lt;TOptions&gt;</code> with our <code>IConfigureOptions</code>\nimplementation as the generic argument</li>\n</ul>\n<p>To start off, we will create the <code>JwtOptionsSetup</code> class:</p>\n<pre><code class=\"language-csharp\">public class JwtOptionsSetup : IConfigureOptions&lt;JwtOptions&gt;\n{\n    private const string SectionName = &quot;Jwt&quot;;\n    private readonly IConfiguration _configuration;\n\n    public JwtOptionsSetup(IConfiguration configuration)\n    {\n        _configuration = configuration;\n    }\n\n    public void Configure(JwtOptions options)\n    {\n        _configuration\n            .GetSection(SectionName)\n            .Bind(options);\n    }\n}\n</code></pre>\n<p>We wrote more code, to achieve the same thing. Was it worth it?</p>\n<p>Perhaps, if you consider that we now have access to <strong>dependency injection</strong> in the <code>JwtOptionsSetup</code> class.\nThis means that we can resolve other services that we can use to get the configuration values.</p>\n<p>We also need to tell the application to use the <code>JwtOptionsSetup</code> class:</p>\n<pre><code class=\"language-csharp\">builder.Services.ConfigureOptions&lt;JwtOptionsSetup&gt;();\n</code></pre>\n<p>When we try to inject our <code>JwtOptions</code> somewhere, the <code>JwtOptionsSetup.Configure</code>\nmethod will be called first the calculate the correct values.</p>\n<h2>Injecting Options With IOptions</h2>\n<p>We've seen a few examples for how to configure the <strong>options pattern</strong> with the <code>JwtOptions</code> class.</p>\n<p>But how do we actually use the <strong>options pattern</strong>?</p>\n<p>Easy, you just need to inject <code>IOptions&lt;JwtOptions&gt;</code> from the constructor.</p>\n<p>I'll just show the <code>JwtProvider</code> constructor here, for brevity.</p>\n<pre><code class=\"language-csharp\">public JwtProvider(IOptions&lt;JwtOptions&gt; options)\n{\n    _options = options.Value;\n}\n</code></pre>\n<p>The actual <code>JwtOptions</code> instance is available on the <code>IOptions&lt;JwtOptions&gt;.Value</code> property.</p>\n<p>The <code>IOptions</code> instance that we injected here is configured\nas a <strong>Singleton</strong> in dependency injection. This is very important to be aware of.</p>\n<h2>What About IOptionsSnapshot and IOptionsMonitor?</h2>\n<p>If you want to use the latest configuration values every time\nyou inject an <strong>options</strong> class, then injecting <code>IOptions</code> won't work.</p>\n<p>However, you can use the <code>IOptionsSnapshot</code> interface instead:</p>\n<ul>\n<li>It provides the latest configuration snapshot (cached per request)</li>\n<li>It is registered as a <strong>Scoped</strong> service</li>\n<li>It detects configuration changes after application start</li>\n</ul>\n<p>You can also use the <code>IOptionsMonitor</code> which retrieves the current\noption values at any time, and it's a <strong>Singleton</strong> service.</p>\n<h2>Wrapping up</h2>\n<p>The <strong>options pattern</strong> gives us a way to use strongly typed configuration classes in our application.</p>\n<p>We can configure the options class in a simple way with <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7#setting-up-options-pattern-using-iconfiguration\"><code>IConfiguration</code></a>,\nor we can create an <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7#setting-up-options-pattern-using-iconfigureoptions\"><code>IConfigureOptions</code></a>\nimplementation if we need something more powerful.</p>\n<p>When it comes to using the <strong>options pattern</strong>, we have three approaches:</p>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptions-1?view=dotnet-plat-ext-7.0\"><code>IOptions</code></a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptionssnapshot-1?view=dotnet-plat-ext-7.0\"><code>IOptionsSnapshot</code></a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptionsmonitor-1?view=dotnet-plat-ext-7.0\"><code>IOptionsMonitor</code></a></li>\n</ul>\n<p>Deciding which of them to use in your application depends on what kind of behavior you want.\nIf you don't need to support refreshing configuration values\nafter application start, <a href=\"https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7#injecting-options-with-ioptions\"><code>IOptions</code></a> is a perfect solution.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-the-options-pattern-in-asp-net-core-7",
            "title": "How To Use The Options Pattern In ASP.NET Core 7",
            "summary": "In this week's newsletter I want to show you how you can use the powerful Options pattern in ASP.NET Core 7.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_012.png",
            "date_modified": "2022-11-19T00:00:00.000Z",
            "date_published": "2022-11-19T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/whats-new-in-dotnet-7",
            "content_html": "<p><strong>.NET 7</strong> was released on November 8th, 2022, alongside <strong>C# 11</strong>.\nThe language highlights are <code>required</code> members, generic attributes, static abstract members in interfaces, and the <code>file</code> keyword.\nOn the library side, LINQ added the <code>Order</code> and <code>OrderDescending</code> methods, which sort an <code>IEnumerable</code> without a key selector.</p>\n<p>In this week's newsletter I want to highlight a few interesting things\nthat are now available with the release of <strong>C# 11</strong> and <strong>.NET 7</strong>.</p>\n<p>In case you missed it, <strong>.NET 7</strong> was released November 8th.</p>\n<p>There are many new features, and you can be sure I had a hard time choosing which ones to highlight.</p>\n<p>Here's what we are going to cover:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/whats-new-in-dotnet-7#required-members\">Required members</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/whats-new-in-dotnet-7#generic-attributes\">Generic attributes</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/whats-new-in-dotnet-7#static-abstract-members-in-interfaces\">Static abstract members in interfaces</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/whats-new-in-dotnet-7#file-keyword\"><code>file</code> keyword</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/whats-new-in-dotnet-7#linq-order-and-orderdescending\">LINQ Order and OrderDescending</a></li>\n</ul>\n<p>Let's see what the new features look like!</p>\n<h2>Required Members</h2>\n<p>We can now define a class member as required by using the <code>required</code> keyword.\nIt can be applied to a <em>field</em> or <em>property</em> and it tells the compiler\nthese members must be initialized by all constructors or by the object initializer.</p>\n<p>Why is this useful?</p>\n<p>Before <strong>C# 11</strong>, the only way to enforce a property being set was through a constructor.\nIf you used an object initializer you could bypass the constructor and not initialize some properties.</p>\n<p>Here's how you can say that a property is required:</p>\n<pre><code class=\"language-csharp\">public class ContentCreator\n{\n    public required string Firstname { get; init; }\n    public string? MiddleName { get; init; }\n    public required string LastName { get; init; }\n}\n</code></pre>\n<p>If you try to create a new <code>ContentCreator</code> instance without initializing\nthe <code>required</code> properties you get a compile error:</p>\n<pre><code class=\"language-csharp\">var creator = new ContentCreator\n{\n    FirstName = &quot;Milan&quot; // Error: No LastName\n}\n</code></pre>\n<h2>Generic Attributes</h2>\n<p>You can now declare a <em>generic</em> class whose base class is <code>Attribute</code>.</p>\n<p>Before <strong>C# 11</strong>, if you wanted to pass in a type as a parameter\nto an <code>Attribute</code> you would need to pass it through the constructor:</p>\n<pre><code class=\"language-csharp\">public class TypedAttribute : Attribute\n{\n    public TypedAttribute(Type t) =&gt; Param = t;\n\n    public Type Param { get; }\n}\n</code></pre>\n<p>And here's how you would use it with the <code>typeof</code> operator:</p>\n<pre><code class=\"language-csharp\">[TypedAttribute(typeof(int))]\npublic int Method() =&gt; default;\n</code></pre>\n<p>Using the generic attributes feature, you can now define it like this:</p>\n<pre><code class=\"language-csharp\">public class TypedAttribute&lt;T&gt; : Attribute { ... }\n</code></pre>\n<p>Now, we can specify the type parameter as a generic argument:</p>\n<pre><code class=\"language-csharp\">[TypedAttribute&lt;int&gt;()]\npublic int Method() =&gt; default;\n</code></pre>\n<h2>Static Abstract Members in Interfaces</h2>\n<p>This is a very interesting feature that allows abstracting of static operations.\nAn example of this would be operators.</p>\n<pre><code class=\"language-csharp\">public interface IMonoid&lt;TSelf&gt; where TSelf : IMonoid&lt;TSelf&gt;\n{\n    public static abstract TSelf operator +(TSelf a, TSelf b);\n\n    public static abstract TSelf Zero { get; }\n}\n</code></pre>\n<p>How can we use the <code>IMonoid</code> interface?</p>\n<p>It may be confusing at first, since the members are virtual\nand there is no instance to call the virtual members on.\nThe solution is to use generics and let the compiler infer the rest.</p>\n<p>Here's a simple example:</p>\n<pre><code class=\"language-csharp\">T AddAll&lt;T&gt;(params T[] elements) where T : IMonoid&lt;T&gt;\n{\n    T result = T.Zero;\n\n    foreach (var element in elements)\n    {\n         result += element;\n    }\n\n    return result;\n}\n</code></pre>\n<p>If you want to learn more, check out the docs on\n<a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interface-implementation/static-virtual-interface-members#static-abstract-interface-methods\">static abstract interface methods</a>\nand <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interface-implementation/static-virtual-interface-members#generic-math\">generic math</a>.</p>\n<h2>File Keyword</h2>\n<p>With the new <code>file</code> keyword you can define a type whose scope and visibility\nis restricted to the file in which it is declared.</p>\n<pre><code class=\"language-csharp\">file class HiddenClass\n{\n}\n</code></pre>\n<p>This feature is practical when used inside of source generators, to avoid collisions when naming generated types.</p>\n<p>But you may be able to find a use for it in your application.</p>\n<h2>LINQ Order and OrderDescending</h2>\n<p>The new <code>Order</code> and <code>OrderDescending</code> methods allow us to sort an\n<code>IEnumerable</code>, which simplifies the code for sorting.</p>\n<p>Here's an example of ordering an array:</p>\n<pre><code class=\"language-csharp\">var array = new[] { 19, 91, 21 };\n\nvar arrayAsc = array.Order();\n\nvar arrayDesc = array.OrderDescending();\n</code></pre>\n<p>I want to highlight that <code>IQueryable</code> also supports the new methods.</p>\n<h2>Will You Upgrade to .NET 7?</h2>\n<p><strong>.NET 7</strong> is not an LTS (Long Term Support) release,\nand will be in support until May 2024,\nwith <strong>.NET 8</strong> releasing in November 2023.</p>\n<p>Here are a few reasons why you should consider upgrading:</p>\n<ul>\n<li>Major performance improvements</li>\n<li>New features in <strong>.NET 7</strong></li>\n<li>New features in <strong>EF Core 7</strong></li>\n<li>Easier migration to <strong>.NET 8</strong></li>\n</ul>\n<p>I will be moving some of my new projects from <strong>.NET 6</strong> to <strong>.NET 7</strong>.</p>\n<p>And I will also upgrade all of my YouTube content to <strong>.NET 7</strong>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/whats-new-in-dotnet-7",
            "title": "What's New In .NET 7?",
            "summary": "In this week's newsletter I want to highlight a few interesting things that are now available with the release of C# 11 and .NET 7.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_011.png",
            "date_modified": "2022-11-12T00:00:00.000Z",
            "date_published": "2022-11-12T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections",
            "content_html": "<p>The fastest way to check a collection for duplicates is a single pass that adds every element to a <code>HashSet</code> and stops when <code>Add</code> returns false.\nThat is O(n), and in my benchmarks the plain <code>foreach</code> version was the clear winner.\nThe LINQ <code>Any</code> and <code>All</code> variants do the same work in one line.</p>\n<p>In this week's newsletter, we will take a look at five different ways to check if a collection <strong>contains duplicates</strong>.</p>\n<p>I'm going to explain the idea behind each <strong>algorithm</strong>, discuss the <strong>algorithm complexity</strong> (Big O Notation), and at the end, we'll look at some <strong>benchmark results</strong>.</p>\n<p>The five approaches for finding a duplicate will use the:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections#check-for-duplicates-with-foreach-loop\"><code>foreach</code></a> loop</li>\n<li>LINQ <a href=\"https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections#check-for-duplicates-with-linq-any\"><code>Any</code></a> method</li>\n<li>LINQ <a href=\"https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections#check-for-duplicates-with-linq-all\"><code>All</code></a> method</li>\n<li>LINQ <a href=\"https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections#check-for-duplicates-with-linq-distinct\"><code>Distinct</code></a> method</li>\n<li>LINQ <a href=\"https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections#check-for-duplicates-with-linq-tohashset\"><code>ToHashSet</code></a> method</li>\n</ul>\n<p>Let's see how we can implement each approach!</p>\n<h2>Check For Duplicates With ForEach Loop</h2>\n<p>The first implementation will use the <code>foreach</code> loop and the <code>HashSet</code> data structure.</p>\n<p>Here's the code for the <code>ContainsDuplicates</code> method:</p>\n<pre><code class=\"language-csharp\">public bool ContainsDuplicates&lt;T&gt;(IEnumerable&lt;T&gt; enumerable)\n{\n   HashSet&lt;T&gt; set = new();\n\n   foreach(var element in enumerable)\n   {\n      if (!set.Add(element))\n      {\n         return true;\n      }\n   }\n\n   return false;\n}\n</code></pre>\n<p>The idea is simple:</p>\n<ul>\n<li>Loop through the collection</li>\n<li>Add each element to the <code>HashSet</code></li>\n<li>When <code>HashSet.Add</code> returns false we found a duplicate</li>\n<li>If we loop through the entire collection there are no duplicates</li>\n</ul>\n<p>In terms of <strong>algorithm complexity</strong>, this would be <strong>O(n)</strong> or linear complexity.\nThis is because there's only one iteration through the collection.</p>\n<p>Adding an element to a <code>HashSet</code> is a constant operation - <strong>O(1)</strong>.\nSo it doesn't affect the overall complexity.</p>\n<h2>Check For Duplicates With LINQ Any</h2>\n<p>We'll combine the idea from the previous implementation of\nusing the <code>HashSet</code> and pair it with the LINQ <code>Any</code>\nmethod to iterate over the collection.</p>\n<p>Here's the implementation for the <code>ContainsDuplicates</code> method:</p>\n<pre><code class=\"language-csharp\">public bool ContainsDuplicates&lt;T&gt;(IEnumerable&lt;T&gt; enumerable)\n{\n   HashSet&lt;T&gt; set = new();\n\n   return enumerable.Any(element =&gt; !set.Add(element));\n}\n</code></pre>\n<p>You can see this implementation is significantly shorter.\nBut it works the same as the one with the <code>foreach</code> loop.</p>\n<p>If any element in the collection satisfies the specified expression,\n<code>Any</code> will <em>short-circuit</em> and return <code>true</code>.\nOtherwise, it will iterate over the entire collection and return <code>false</code>.</p>\n<p>We're still looking at linear complexity here, <strong>O(n)</strong>.</p>\n<h2>Check For Duplicates With LINQ All</h2>\n<p>For our third implementation, we will use the opposite\nof the LINQ <code>Any</code> method - the LINQ <code>All</code> method.</p>\n<p>Here's the implementation with LINQ <code>All</code>:</p>\n<pre><code class=\"language-csharp\">public bool ContainsDuplicates&lt;T&gt;(IEnumerable&lt;T&gt; enumerable)\n{\n   HashSet&lt;T&gt; set = new();\n\n   return !enumerable.All(set.Add);\n}\n</code></pre>\n<p>The idea here is a little different than in the previous implementation.</p>\n<p><code>All</code> will return <code>true</code> if all elements in a collection\nsatisfy the specified expression.</p>\n<p>If at least one element doesn't satisfy the condition -\nin our case when a <strong>duplicate</strong> is found - it will <em>short-circuit</em> and return <code>false</code>.</p>\n<p>This is still linear complexity, <strong>O(n)</strong>.</p>\n<h2>Check For Duplicates With LINQ Distinct</h2>\n<p>So far, we've seen a few implementations using the <code>HashSet</code> data structure.\nNow let's consider a different approach.</p>\n<p>We can use the LINQ <code>Distinct</code> method to check for duplicates.</p>\n<p>Here's the code for the <code>ContainsDuplicates</code> method:</p>\n<pre><code class=\"language-csharp\">public bool ContainsDuplicates&lt;T&gt;(IEnumerable&lt;T&gt; enumerable)\n{\n   return enumerable.Distinct().Count() != enumerable.Count();\n}\n</code></pre>\n<p>The idea is first find the <code>Distinct</code> elements and <code>Count</code> them,\nand then compare that to the number of all elements.</p>\n<p>If the number of distinct elements is not equal to\nthe number of all elements, we have a <strong>duplicate</strong> value.</p>\n<p>In terms of <strong>algorithm complexity</strong>, this is still linear complexity.</p>\n<p>But we have at least two iterations through the collection\nor three in the worst-case scenario.</p>\n<p>We have one iteration for <code>Distinct</code> and one more\niteration for the call to <code>Count</code> right after that.\nThe last call to <code>Count</code> can return in constant time,\nif the collection is an <code>array</code> or <code>List</code>.</p>\n<h2>Check For Duplicates With LINQ ToHashSet</h2>\n<p>For the last implementation we will use the LINQ <code>ToHashSet</code> method.</p>\n<p>It takes a collection and creates a <code>HashSet</code> instance from it.</p>\n<p>Here's what the <code>ContainsDuplicates</code> implementation looks like:</p>\n<pre><code class=\"language-csharp\">public bool ContainsDuplicates&lt;T&gt;(IEnumerable&lt;T&gt; enumerable)\n{\n   return enumerable.ToHashSet().Count != enumerable.Count();\n}\n</code></pre>\n<p>We compare the number of elements in the <code>HashSet</code> to the number of elements in the collection.</p>\n<p>If they are different, we have a <strong>duplicate</strong> value.</p>\n<p>This is also linear complexity, <strong>O(n)</strong>.</p>\n<h2>Benchmark Results</h2>\n<p>Now that we've seen our implementations let's put them to the test.</p>\n<p>I ran the benchmark for collections of varying sizes:</p>\n<ul>\n<li>100</li>\n<li>1,000</li>\n<li>10,000</li>\n</ul>\n<p>Each collection contains exactly one duplicate value located somewhere around the middle of the collection.</p>\n<p>Here are the results:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_010/benchmark.png\" alt=\"Benchmark comparing five duplicate-detection methods across collection sizes, with foreach consistently fastest\">\n<p>The approach using the <code>foreach</code> loop comes out as the clear winner in terms of performance.</p>\n<p>However, I would lean towards using the implementations with LINQ <code>Any</code> or <code>All</code> because of their simplicity.</p>\n<p>You can find the <a href=\"https://github.com/m-jovanovic/find-duplicates-benchmark\">source code for the benchmark</a>\non my GitHub. Feel free to submit a PR with a faster implementation if you can think of one.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/5-ways-to-check-for-duplicates-in-collections",
            "title": "5 Ways To Check For Duplicates In Collections, With Benchmarks",
            "summary": "In this week's newsletter, we will take a look at five different ways to check if a collection contains duplicates.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_010.png",
            "date_modified": "2022-11-05T00:00:00.000Z",
            "date_published": "2022-11-05T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core",
            "content_html": "<p>A global query filter is a condition you configure once with <code>HasQueryFilter</code> inside <code>OnModelCreating</code>, and EF Core applies it to every query for that entity type.\nIt removes repetitive conditions like the soft-delete check or a <code>tenantId</code> filter from your queries.\nCall <code>IgnoreQueryFilters</code> when a single query needs to skip it.</p>\n<p>In this week's newsletter, I'll show you how you can remove repetitive conditions in <strong>EF Core</strong> database queries.</p>\n<p>Which kinds of queries fit this description?</p>\n<p>An example would be when you implement <a href=\"https://milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core\"><strong>soft-delete</strong></a>, and have to check if a record was <strong>soft-deleted</strong> or not in every query.</p>\n<p>Also, it's practical if you're working in a <a href=\"https://milanjovanovic.tech/blog/multi-tenant-applications-with-ef-core\"><strong>multi-tenant system</strong></a> and need to specify a <code>tenantId</code> on every query.</p>\n<p><strong>EF Core</strong> has a powerful feature that can help you remove repetitive conditions from your code.</p>\n<p>I'm talking about <a href=\"https://learn.microsoft.com/en-us/ef/core/querying/filters\">Query Filters</a>.</p>\n<p>Let's see how we can implement it.</p>\n<h2>How To Apply Query Filters</h2>\n<p>Before introducing <strong>Query Filters</strong>, we will see how the standard approach looks.\nWe have an <code>Orders</code> table that supports <strong>soft-deleting</strong>.\nAnd we never want to return <strong>soft-deleted</strong> orders.</p>\n<p>We'll start with an <code>Order</code> entity that has an <code>IsDeleted</code> property.</p>\n<pre><code class=\"language-csharp\">public class Order\n{\n   public int Id { get; set; }\n   public bool IsDeleted { get; set; }\n}\n</code></pre>\n<p>And we have a business requirement that we can only query orders that are not deleted.</p>\n<p>Here's what an <strong>EF</strong> query to get a single <code>Order</code> might look like:</p>\n<pre><code class=\"language-csharp\">dbContext\n   .Orders\n   .Where(order =&gt; !order.IsDeleted)\n   .Where(order =&gt; order.Id == orderId)\n   .FirstOrDefault();\n</code></pre>\n<p>This works perfectly for what we need to do.</p>\n<p>However, we need to remember to apply this condition every time we want to query the <code>Order</code> entity.</p>\n<p>Now, let's see how we can define a <strong>Query Filter</strong> on the <code>Order</code> entity to\napply this check when querying the database.</p>\n<p>Inside of the <code>OnModelCreating</code> method on the database context, we need to\ncall the <code>HasQueryFilter</code> method and specify the expression we want:</p>\n<pre><code class=\"language-csharp\">modelBuilder\n   .Entity&lt;Order&gt;()\n   .HasQueryFilter(order =&gt; !order.IsDeleted);\n</code></pre>\n<p>Now we can omit the <strong>soft-delete</strong> check from the previous <strong>LINQ</strong> expression:</p>\n<pre><code class=\"language-csharp\">dbContext\n   .Orders\n   .Where(order =&gt; order.Id == orderId)\n   .FirstOrDefault();\n</code></pre>\n<p>And this is the <strong>SQL</strong> that <strong>EF</strong> will generate with the <strong>Query Filter</strong>:</p>\n<pre><code class=\"language-sql\">SELECT o.*\nFROM Orders o\nWHERE o.IsDeleted = FALSE AND o.Id = @orderId\n</code></pre>\n<h2>Disabling Query Filters</h2>\n<p>You may run into a situation where you need to disable <strong>Query Filters</strong> for a specific query.\nLuckily, there is an easy way to do this.</p>\n<p>In your <strong>LINQ</strong> expression, you need to call the <code>IgnoreQueryFilters</code> method,\nand all the <strong>Query Filters</strong> configured for this entity will be disabled:</p>\n<pre><code class=\"language-csharp\">dbContext\n   .Orders\n   .IgnoreQueryFilters()\n   .Where(order =&gt; order.Id == orderId)\n   .FirstOrDefault();\n</code></pre>\n<p>Be careful when doing this, as you can easily introduce unwanted behavior in your application.</p>\n<h2>Good Things To Know Before Using Query Filters</h2>\n<p>Here are a few more details that you should know about <strong>Query Filters</strong> before using them.\nHopefully, this will save you some trouble if you decide to use them in your application.</p>\n<p><strong>Configuring multiple Query Filters</strong></p>\n<p>Configuring multiple <strong>Query Filters</strong> on the same entity will only apply the last one.\nIf you need more than one condition, you can do that with the logical <code>AND</code> operator (&amp;&amp;).</p>\n<p><strong>Ignoring specific Query Filters</strong></p>\n<p>If you need to ignore a specific expression in a <strong>Query Filter</strong> and leave the rest in place,\nunfortunately, you can't do that. Only one <strong>Query Filter</strong> is allowed per entity type.</p>\n<p>One solution is calling <code>IgnoreQueryFilters</code>, which will remove the configured <strong>Query Filter</strong>\nfor that entity type. And then manually apply the condition that you need for that specific query.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-use-global-query-filters-in-ef-core",
            "title": "How To Use Global Query Filters in EF Core",
            "summary": "In this week's newsletter, I'll show you how to remove repetitive conditions from your EF Core queries, like the soft-delete check or the tenantId filter you…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_009.png",
            "date_modified": "2022-10-29T00:00:00.000Z",
            "date_published": "2022-10-29T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6",
            "content_html": "<p>Locking is how you stop two threads from running the same block of code at the same time, so concurrent updates to a shared resource stay correct.\n.NET gives you the <code>lock</code> statement and the <code>Semaphore</code> class for synchronous code, and <code>SemaphoreSlim</code> when the critical section contains <code>async</code> calls.\n<code>Monitor</code>, <code>Mutex</code>, and <code>ReaderWriterLock</code> are the other options.</p>\n<p>In this week's newsletter, we'll see how we can work with <strong>locking</strong> in <strong>.NET 6</strong>.</p>\n<p>We won't talk about how the lock is actually implemented at the operating system level.\nInstead, I will focus on application-level <strong>locking</strong> mechanisms.</p>\n<p><strong>Locking</strong> allows us to control how many <strong>threads</strong> can access some piece of code.\nWhy would you want to do this?</p>\n<p>Usually because you want to protect access to <strong>expensive resources</strong>,\nand you need the <strong>concurrency control</strong> that locking enables.</p>\n<p>We will use a simple <code>BankAccount</code> class with a <code>Deposit</code> method\nto illustrate how to implement locking.</p>\n<h2>The C# Lock Statement</h2>\n<p>The C# language supports locking with the <a href=\"https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock\"><code>lock</code> statement</a>.\nYou can use the <code>lock</code> statement to define a code block that only one thread can access.</p>\n<p>The <code>lock</code> statement acquires a mutual-exclusion lock (mutex) for a given object,\nexecutes the statement block, and releases the lock.</p>\n<pre><code class=\"language-csharp\">lock(_lock)\n{\n   // Your code...\n}\n</code></pre>\n<p>Here <code>_lock</code> is a reference type, usually an <code>object</code> instance.</p>\n<p>Let's see how we can implement the <code>BankAccount</code> class using the <code>lock</code> statement:</p>\n<pre><code class=\"language-csharp\">public class BankAccount\n{\n   private static readonly object _lock = new();\n   private decimal _balance;\n\n   public void Deposit(decimal amount)\n   {\n      lock(_lock)\n      {\n         _balance += amount;\n      }\n   }\n}\n</code></pre>\n<p>The first thread to reach and execute the <code>lock</code> statement will be allowed to update\nthe <code>_balance</code>. Any other threads will block until the lock is released.</p>\n<h2>Locking With Semaphore</h2>\n<p>The <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphore?view=net-6.0\"><code>Semaphore</code></a>\nclass is another option we can use to achieve the same effect.</p>\n<p>We'll use the <code>Semaphore</code> constructor to set the <code>initialCount</code> to 1,\nwhich means that the <code>Semaphore</code> is open at the start.\nAnd we will also set the <code>maximumCount</code> to 1,\nwhich means that only one thread is allowed to enter the <code>Semaphore</code>.</p>\n<p>Let's see how we can implement the <code>BankAccount</code> class using the <code>Semaphore</code>:</p>\n<pre><code class=\"language-csharp\">public class BankAccount\n{\n   private static readonly Semaphore _semaphore = new(\n      initialCount: 1,\n      maximumCount: 1);\n\n   private decimal _balance;\n\n   public void Deposit(decimal amount)\n   {\n      _semaphore.WaitOne();\n\n      _balance += amount;\n\n      _semaphore.Release();\n   }\n}\n</code></pre>\n<p>To enter the <code>Semaphore</code>, we have to call the <code>WaitOne</code> method.</p>\n<p>If no thread was previously inside, our thread is allowed\nto enter the <code>Semaphore</code> and update the balance.</p>\n<p>After updating the balance, we call the <code>Release</code> method to\nrelease the <code>Semaphore</code> for other threads that might be waiting.</p>\n<h2>Asynchronous Locking With SemaphoreSlim</h2>\n<p>What if we wanted to call an asynchronous method in a locked context?</p>\n<p>We can't use the <code>lock</code> statement as it doesn't support asynchronous calls.\nAwaiting an asynchronous call inside a <code>lock</code> statement will cause a compilation error.</p>\n<p>The <code>Semaphore</code> class can solve this problem.</p>\n<p>But I want to show you another option that we have, <a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=net-6.0\"><code>SemaphoreSlim</code></a>.\nIt's a lightweight alternative to the <code>Semaphore</code> class and has <code>async</code> methods.</p>\n<p>Let's see how we can implement the <code>BankAccount</code> class using <code>SemaphoreSlim</code>:</p>\n<pre><code class=\"language-csharp\">public class BankAccount\n{\n   private static readonly SemaphoreSlim _semaphore = new(\n      initialCount: 1,\n      maximumCount: 1);\n\n   private decimal _balance;\n\n   public async Task Deposit(decimal amount)\n   {\n      await _semaphore.WaitAsync();\n\n      _balance += amount;\n\n      _semaphore.Release();\n   }\n}\n</code></pre>\n<p>Notice that I updated the <code>Deposit</code> method to return a <code>Task</code>.</p>\n<p>This time, we're calling <code>WaitAsync</code> to block the current\nthread until it can enter the semaphore.</p>\n<p>After updating the balance, we call the <code>Release</code> method\nto release the <code>SemaphoreSlim</code> like in the previous example.</p>\n<h2>Are There Other Options For Locking in .NET?</h2>\n<p>So far I mentioned three options to implement locking:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6#the-c-lock-statement\"><code>lock</code> statement</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6#locking-with-semaphore\"><code>Semaphore</code></a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6#asynchronous-locking-with-semaphoreslim\"><code>SemaphoreSlim</code></a></li>\n</ul>\n<p>However, <strong>.NET</strong> has other classes for <strong>concurrency control</strong> that you can\nexplore like\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.monitor?view=net-6.0\"><code>Monitor</code></a>,\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=net-6.0\"><code>Mutex</code></a>,\n<a href=\"https://learn.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlock?view=net-6.0\"><code>ReaderWriterLock</code></a>\nand many more.</p>\n<p>I hope you enjoyed this brief introduction to a very complex topic.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6",
            "title": "Introduction To Locking And Concurrency Control in .NET 6",
            "summary": "In this week's newsletter, we'll see how we can work with locking in .NET 6. We won't cover how the lock is implemented at the operating system level, only the…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_008.png",
            "date_modified": "2022-10-22T00:00:00.000Z",
            "date_published": "2022-10-22T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster",
            "content_html": "<p>I made an API endpoint 15x faster by finding the bottlenecks first, then fixing them one at a time.\nThat meant reducing database round trips, parallelizing independent external calls with <code>Task.WhenAll</code>, and leaving caching as a last resort for data that is frequently accessed but rarely modified.</p>\n<p>Performance optimization is my favorite thing about software engineering.\nOver the last 5 years, I've encountered various performance problems\nthat taught me different ways to overcome them.</p>\n<p>About a month ago, I ran into an issue with an API endpoint that wasn't scaling well.</p>\n<p>This endpoint is used to calculate a report for an e-commerce web application.\nIt needed to talk to multiple modules (services) to gather all the necessary data,\ncombine it and perform the calculations.</p>\n<p>I made a <a href=\"https://www.linkedin.com/feed/update/urn:li:activity:6966700329111310336/\">post about it on LinkedIn</a>\nthat resonated with many people.</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_007/linkedin_post.png\" alt=\"LinkedIn post summarizing an API endpoint optimization from 6 seconds to 0.4 seconds\">\n<p>In this newsletter, I want to break down what I did to achieve a <strong>15x performance improvement</strong>.</p>\n<h2>Focus On Bottlenecks First</h2>\n<p>The first thing I do when I'm solving a performance problem\nis determine where the slowest piece of the code is.\nFixing this part of the code will usually give the most significant improvement.</p>\n<p>Solving one bottleneck can also reveal where the next bottleneck is.<br>\nThis is a continual process.</p>\n<p>In my situation, there were a few bottlenecks:</p>\n<ul>\n<li>Calling the database from a loop</li>\n<li>Calling an external service multiple times</li>\n<li>Executing a complex calculation multiple times with identical parameters</li>\n</ul>\n<p>How can you measure performance?</p>\n<p>A simple approach can be using <code>System.Timers.Timer</code> where you\nmanually log execution times between method calls.\nOr you can use a performance profiler.</p>\n<h2>Reduce The Number of Round Trips</h2>\n<p>A round trip between your application and a database\n(or some other service) can last 5-10ms, or more.\nIf you have many round trips in your flow, it's going to add up quickly.</p>\n<p>Here are a few things you can do reduce the number of round trips:</p>\n<ol>\n<li>Don't call the database from a loop. This can usually be solved with a simple query like this:</li>\n</ol>\n<pre><code class=\"language-sql\">   SELECT * FROM [TableName] WHERE Id IN (list_of_ids)\n</code></pre>\n<ol start=\"2\">\n<li>\n<p>Use a query that returns multiple result sets from the database.\nOne library that supports this is <a href=\"https://github.com/DapperLib/Dapper\">Dapper</a>, with the <code>QueryMultiple</code> method.</p>\n</li>\n<li>\n<p>If you need to make multiple calls to another service, try to convert that into one call.\nAnd in the service, aggregate the required data and return everything at once.</p>\n</li>\n</ol>\n<h2>Parallelize External Calls</h2>\n<p>I had a situation where I was awaiting multiple asynchronous calls from a few services.\nThese calls had no dependencies on each other, so I used a simple technique\nto gain a significant performance improvement.</p>\n<p>Let's say you're awaiting two tasks:</p>\n<pre><code class=\"language-csharp\">var task1Result = await CallService1Async();\n\nvar task2Result = await CallService2Async();\n\n// Use the results.\n</code></pre>\n<p>A simple way to parallelize these calls is using the <code>Task.WhenAll</code> method:</p>\n<pre><code class=\"language-csharp\">var task1 = CallService1Async();\n\nvar task2 = CallService2Async();\n\nawait Task.WhenAll(task1, task2);\n\n// Use the results.\ntask1.Result;\ntask2.Result;\n</code></pre>\n<p>Notice that I'm directly accessing the <code>Result</code> property on the tasks.\nThis can be <strong>detrimental</strong> if you're using it to block on an asynchronous call,\nand can even lead to deadlocks.</p>\n<p>However, in this situation it is perfectly safe to do,\nbecause the two tasks will have completed after the call to <code>Task.WhenAll</code> completes.</p>\n<p>Of course, whether or not these tasks will be executed in parallel\nwhen calling <code>Task.WhenAll</code> depends on a few factors, which I won't cover here.</p>\n<h2>Caching As a Last Resort</h2>\n<p>I try to leave <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>caching</strong></a> for the end, after I have exhausted\nall other possibilities to improve performance.\nWhile I love to use caching in general, I'm aware\nit can introduce some unwanted behavior when data is stale.</p>\n<p>You have to consider how long you can safely cache the data,\nand how you are going to clear the cache if the underlying data changes.</p>\n<p>In simple applications, I use <code>IMemoryCache</code>\nthat is available in <strong>ASP.NET Core</strong> out of the box.\nBut you can also use an external cache like <a href=\"https://redis.io/\">Redis</a>.</p>\n<p>A good candidate for caching is data that is frequently accessed, but rarely modified.</p>\n<h2>Closing Thoughts</h2>\n<p>I think that for most Web applications,\nperformance optimization can be boiled down to the following approaches:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster#focus-on-bottlenecks-first\">Focus on bottlenecks first</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster#reduce-the-number-of-round-trips\">Reduce the number of round trips</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster#parallelize-external-calls\">Parallelize external calls</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster#caching-as-a-last-resort\">Caching</a></li>\n</ul>\n<p>I didn't talk about database optimization and indexes here,\nbut this should also be on your mind if the database is your bottleneck.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-i-optimized-an-api-endpoint-to-make-it-15x-faster",
            "title": "How I Optimized an API Endpoint to Make It 15x Faster",
            "summary": "Performance optimizations are my favorite thing about software engineering. Over the last 5 years, I've encountered various performance problems that taught me…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_007.png",
            "date_modified": "2022-10-15T00:00:00.000Z",
            "date_published": "2022-10-15T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/decorator-pattern-in-asp-net-core",
            "content_html": "<p>The <strong>decorator pattern</strong> lets you add new behavior to an existing class without modifying the original class in any way.\nA wrapper class implements the same interface and delegates to the wrapped implementation.\nIn ASP.NET Core you can wire the decorator up manually in the DI container, or register it with a single <code>Decorate</code> call from the Scrutor library.</p>\n<p>Let's imagine we have an existing <code>Repository</code> implementation, and we want to introduce <a href=\"https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance\"><strong>caching</strong></a> to reduce the load on the database.</p>\n<p>How can we achieve this without changing the original <code>Repository</code> implementation?</p>\n<p><strong>Decorator pattern</strong> is a structural design pattern that allows you\nto introduce new behavior to an existing class, without modifying the original class in any way.</p>\n<p>I'll show you how you can implement this with the <strong>ASP.NET Core DI</strong> container.</p>\n<h2>How To Implement The Decorator Pattern</h2>\n<p>We'll start with an existing <code>MemberRepository</code> implementation that implements the <code>IMemberRepository</code> interface.</p>\n<p>It has only one method, which loads the <code>Member</code> from the database.</p>\n<p>Here's what the implementation looks like:</p>\n<pre><code class=\"language-csharp\">public interface IMemberRepository\n{\n    Member GetById(int id);\n}\n\npublic class MemberRepository : IMemberRepository\n{\n    private readonly DatabaseContext _dbContext;\n\n    public MemberRepository(DatabaseContext dbContext)\n    {\n        _dbContext = dbContext;\n    }\n\n    public Member GetById(int id)\n    {\n        return _dbContext\n            .Set&lt;Member&gt;()\n            .First(member =&gt; member.Id == id);\n    }\n}\n</code></pre>\n<p>We want to introduce caching to the <code>MemberRepository</code> implementation without modifying the existing class.</p>\n<p>To achieve this, we can use the <strong>Decorator pattern</strong> and create a wrapper around our <code>MemberRepository</code> implementation.</p>\n<p>We can create a <code>CachingMemberRepository</code> that will have a dependency on <code>IMemberRepository</code>.</p>\n<pre><code class=\"language-csharp\">public class CachingMemberRepository : IMemberRepository\n{\n    private readonly IMemberRepository _repository;\n    private readonly IMemoryCache _cache;\n\n    public CachingMemberRepository(\n        IMemberRepository repository,\n        IMemoryCache cache)\n    {\n        _repository = repository;\n        _cache = cache;\n    }\n\n    public Member GetById(int id)\n    {\n        string key = $&quot;members-{id}&quot;;\n\n        return _cache.GetOrCreate(\n            key,\n            entry =&gt; {\n                entry.SetAbsouluteExpiration(\n                    TimeSpan.FromMinutes(5));\n\n                return _repository.GetById(id);\n            });\n    }\n}\n</code></pre>\n<p>Now I'm going to show you the power of <strong>ASP.NET Core DI</strong>.</p>\n<p>We will configure the <code>IMemberRepository</code> to resolve an instance of <code>CachingMemberRepository</code>,\nwhile it will receive the <code>MemberRepository</code> instance as its dependency.</p>\n<h2>Configuring The Decorator In ASP .NET Core DI</h2>\n<p>For the DI container to be able to resolve <code>IMemberRepository</code> as <code>CachingMemberRepository</code>,\nwe need to manually configure the service.</p>\n<p>We can use the overload that exposes a service provider,\nthat we will use to resolve the services required to construct a <code>MemberRepository</code>.</p>\n<p>Here's what the configuration would look like:</p>\n<pre><code class=\"language-csharp\">services.AddScoped&lt;IMemberRepository&gt;(provider =&gt; {\n    var context = provider.GetService&lt;DatabaseContext&gt;();\n    var cache = provider.GetService&lt;IMemoryCache&gt;();\n\n    return new CachingRepository(\n         new MemberRepository(context),\n         cache);\n});\n</code></pre>\n<p>Now you can inject the <code>IMemberRepository</code>, and the DI will be able to resolve an instance of <code>CachingMemberRepository</code>.</p>\n<h2>Configuring The Decorator With Scrutor</h2>\n<p>If the previous approach seems <em>cumbersome</em> to you and like a lot of manual work - that's because it is.</p>\n<p>However, there is a simpler way to achieve the same behavior.</p>\n<p>We can use the <strong><a href=\"https://github.com/khellang/Scrutor\">Scrutor</a></strong> library to register the decorator:</p>\n<pre><code class=\"language-csharp\">services.AddScoped&lt;IMemberRepository, MemberRepository&gt;();\n\nservices.Decorate&lt;IMemberRepository, CachingMemberRepository&gt;();\n</code></pre>\n<p><strong><a href=\"https://github.com/khellang/Scrutor\">Scrutor</a></strong> exposes the <code>Decorate</code> method.\nThe call to <code>Decorate</code> will register the <code>CachingMemberRepository</code> while ensuring\nthat it receives the expected <code>MemberRepository</code> instance as its dependency.</p>\n<p>I think this approach is much simpler, and it's what I use in my projects.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/decorator-pattern-in-asp-net-core",
            "title": "Decorator Pattern In ASP.NET Core",
            "summary": "Let's imagine we have an existing Repository implementation, and we want to introduce caching without changing the original class.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_006.png",
            "date_modified": "2022-10-08T00:00:00.000Z",
            "date_published": "2022-10-08T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core",
            "content_html": "<p>In this newsletter, we'll be covering three ways to create middleware in <strong>ASP.NET Core</strong> applications.</p>\n<p><strong>Middleware</strong> allows us to introduce additional logic before or after executing an HTTP request.</p>\n<p>You are already using many of the built-in middleware available in the framework.</p>\n<p>I'm going to show you three approaches to how you can define custom middleware:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-middleware-with-request-delegates\">With Request Delegates</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-middleware-by-convention\">By Convention</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core#adding-factory-based-middleware\">Factory-Based</a></li>\n</ul>\n<p>Let's go over each of them and see how we can implement them in code.</p>\n<h2>Adding Middleware With Request Delegates</h2>\n<p>The first approach to defining a middleware is by writing a <strong>Request Delegate</strong>.</p>\n<p>You can do that by calling the <code>Use</code> method on the <code>WebApplication</code> instance\nand providing a lambda method with two arguments.\nThe first argument is the <code>HttpContext</code> and the second argument is\nthe actual next request delegate in the pipeline <code>RequestDelegate</code>.</p>\n<p>Here's what this would look like:</p>\n<pre><code class=\"language-csharp\">var builder = WebApplication.CreateBuilder(args);\nvar app = builder.Build();\n\napp.Use(async (context, next) =&gt;\n{\n    // Add code before request.\n\n    await next(context);\n\n    // Add code after request.\n});\n</code></pre>\n<p>By awaiting the <code>next</code> delegate, you are continuing the request pipeline execution.\nYou can <em>short-circuit</em> the pipeline by not invoking the <code>next</code> delegate.</p>\n<p>This overload of the <code>Use</code> method is the one suggested by <strong>Microsoft</strong>.</p>\n<h2>Adding Middleware By Convention</h2>\n<p>The second approach requires us to create a class that will represent our middleware.\nWe have to follow the convention when creating this class so that we can use it as middleware in our application.</p>\n<p>I'm first going to show you what this class looks like, and then explain what is the convention we are following here.</p>\n<p>Here's how this class would look like:</p>\n<pre><code class=\"language-csharp\">public class ConventionMiddleware(\n    RequestDelegate next,\n    ILogger&lt;ConventionMiddleware&gt; logger)\n{\n    public async Task InvokeAsync(HttpContext context)\n    {\n        logger.LogInformation(&quot;Before request&quot;);\n\n        await next(context);\n\n        logger.LogInformation(&quot;After request&quot;);\n    }\n}\n</code></pre>\n<p>The convention we are following has a few rules:</p>\n<ul>\n<li>We need to inject a <code>RequestDelegate</code> in the constructor</li>\n<li>We need to define an <code>InvokeAsync</code> method with an <code>HttpContext</code> argument</li>\n<li>We need to invoke the <code>RequestDelegate</code> and pass it the <code>HttpContext</code> instance</li>\n</ul>\n<p>There's one more thing that's required, and that is to tell our application to use this middleware.</p>\n<p>We can do that by calling the <code>UseMiddleware</code> method:</p>\n<pre><code class=\"language-csharp\">app.UseMiddleware&lt;ConventionMiddleware&gt;();\n</code></pre>\n<p>And with this, we have a functioning middleware.</p>\n<h2>Adding Factory-Based Middleware</h2>\n<p>The third and last approach requires us to also create a class that will represent our middleware.</p>\n<p>However, this time we're going to implement the <code>IMiddleware</code> interface.\nThis interface has only one method - <code>InvokeAsync</code>.</p>\n<p>Here's what this class would like:</p>\n<pre><code class=\"language-csharp\">public class FactoryMiddleware(ILogger&lt;FactoryMiddleware&gt; logger) : IMiddleware\n{\n    public async Task InvokeAsync(HttpContext context, RequestDelegate next)\n    {\n        logger.LogInformation(&quot;Before request&quot;);\n\n        await next(context);\n\n        logger.LogInformation(&quot;After request&quot;);\n    }\n}\n</code></pre>\n<p>The <code>FactoryMiddleware</code> class will be resolved at runtime from dependency injection.</p>\n<p>Because of this, we need to register it as a service:</p>\n<pre><code class=\"language-csharp\">builder.Services.AddTransient&lt;FactoryMiddleware&gt;();\n</code></pre>\n<p>And like the previous example, we need to tell our application to use our factory-based middleware:</p>\n<pre><code class=\"language-csharp\">app.UseMiddleware&lt;FactoryMiddleware&gt;();\n</code></pre>\n<p>With this, we have a functioning middleware.</p>\n<h2>A Word On Strong Typing</h2>\n<p>I'm a big fan of <strong>strong typing</strong> whenever possible.\nOut of the three approaches I just showed you, the one using the\n<code>IMiddleware</code> interface satisfies this constraint the most.\nThis is also my <strong>preferred</strong> way to implement <strong>middleware</strong>.</p>\n<p>Since we're implementing an interface, it's very easy to create\na generic solution to never forget to register your middleware.</p>\n<p>You can use reflection to scan for classes implementing\nthe <code>IMiddleware</code> interface and add them to dependency injection,\nand also add them to the application by calling <code>UseMiddleware</code>.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/3-ways-to-create-middleware-in-asp-net-core",
            "title": "3 Ways To Create Middleware In ASP.NET Core",
            "summary": "Middleware allows us to introduce additional logic before or after executing an HTTP request.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_005.png",
            "date_modified": "2022-10-01T00:00:00.000Z",
            "date_published": "2022-10-01T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/clean-architecture-folder-structure",
            "content_html": "<p><a href=\"https://milanjovanovic.tech/blog/clean-architecture-dotnet\"><strong>Clean Architecture</strong></a> is a popular approach to structuring your application.</p>\n<p>It's a layered architecture that splits the project into four layers:</p>\n<ul>\n<li><a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure#domain-layer\">Domain</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure#application-layer\">Application</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure#infrastructure-layer\">Infrastructure</a></li>\n<li><a href=\"https://milanjovanovic.tech/blog/clean-architecture-folder-structure#presentation-layer\">Presentation</a></li>\n</ul>\n<p>Each of the layers is typically one project in your solution.</p>\n<p>Here's a visual representation of the <strong>Clean Architecture</strong>:</p>\n<img src=\"https://milanjovanovic.tech/blogs/mnw_004/clean_architecture.png\" alt=\"Clean Architecture layers with Domain at the center, surrounded by Application, Presentation, and Infrastructure\">\n<p>How do we create this in our .NET solutions?</p>\n<h2>Domain Layer</h2>\n<p>The <a href=\"https://milanjovanovic.tech/blog/domain-layer-clean-architecture\"><strong>Domain layer</strong></a> sits at the core of the <strong>Clean Architecture</strong>.\nHere we define things like: entities, value objects, aggregates, domain events, exceptions, repository interfaces, etc.</p>\n<p>Here is the folder structure I like to use:</p>\n<pre><code>📁 Domain\n|__ 📁 DomainEvents\n|__ 📁 Entities\n|__ 📁 Exceptions\n|__ 📁 Repositories\n|__ 📁 Shared\n|__ 📁 ValueObjects\n</code></pre>\n<p>You can introduce more things here if you think it's required.</p>\n<p>One thing to note is that the <strong>Domain layer</strong> is not allowed to reference other projects in your solution.</p>\n<h2>Application Layer</h2>\n<p>The <a href=\"https://milanjovanovic.tech/blog/application-layer-clean-architecture\"><strong>Application layer</strong></a> sits right above the <strong>Domain layer</strong>.\nIt acts as an orchestrator for the <strong>Domain layer</strong>, containing the most important use cases in your application.</p>\n<p>You can structure your use cases using services or using commands and queries.</p>\n<p>I'm a big fan of the <strong>CQRS</strong> pattern, so I like to use the command and query approach.</p>\n<p>Here is the folder structure I like to use:</p>\n<pre><code>📁 Application\n|__ 📁 Abstractions\n    |__ 📁 Data\n    |__ 📁 Email\n    |__ 📁 Messaging\n|__ 📁 Behaviors\n|__ 📁 Contracts\n|__ 📁 Entity1\n    |__ 📁 Commands\n    |__ 📁 Events\n    |__ 📁 Queries\n|__ 📁 Entity2\n    |__ 📁 Commands\n    |__ 📁 Events\n    |__ 📁 Queries\n</code></pre>\n<p>In the <code>Abstractions</code> folder, I define the interfaces required for the <strong>Application layer</strong>.\nThe implementations for these interfaces are in one of the upper layers.</p>\n<p>For every entity in the <strong>Domain layer</strong>, I create one folder with the commands, queries, and events definitions.</p>\n<h2>Infrastructure Layer</h2>\n<p>The <strong>Infrastructure layer</strong> contains implementations for external-facing services.</p>\n<p>What would fall into this category?</p>\n<ul>\n<li>Databases - PostgreSQL, MongoDB</li>\n<li>Identity providers - Auth0, Keycloak</li>\n<li>Emails providers</li>\n<li>Storage services - AWS S3, Azure Blob Storage</li>\n<li>Message queues - Rabbit MQ</li>\n</ul>\n<p>Here is the folder structure I like to use:</p>\n<pre><code>📁 Infrastructure\n|__ 📁 BackgroundJobs\n|__ 📁 Services\n    |__ 📁 Email\n    |__ 📁 Messaging\n|__ 📁 Persistence\n    |__ 📁 EntityConfigurations\n    |__ 📁 Migrations\n    |__ 📁 Repositories\n    |__ #️⃣ ApplicationDbContext.cs\n|__ 📁 ...\n</code></pre>\n<p>I place my <code>DbContext</code> implementation here if I'm using <strong>EF Core</strong>.</p>\n<p>It's not uncommon to make the Persistence folder its project.\nI frequently do this to have all database facing-code inside of one project.</p>\n<h2>Presentation Layer</h2>\n<p>The <strong>Presentation layer</strong> is the entry point to our system.\nTypically, you would implement this as a Web API project.</p>\n<p>The most important part of the <strong>Presentation layer</strong> is the <code>Controllers</code>, which define the API endpoints in our system.</p>\n<p>Here is the folder structure I like to use:</p>\n<pre><code>📁 Presentation\n|__ 📁 Controllers\n|__ 📁 Middlewares\n|__ 📁 ViewModels\n|__ 📁 ...\n|__ #️⃣ Program.cs\n</code></pre>\n<p>Sometimes, I will move the <strong>Presentation layer</strong> away from the actual Web API project.\nI do this to isolate the <code>Controllers</code> and enforce stricter constraints.\nYou don't have to do this if it is too complicated for you.</p>\n<h2>Is This The Only Way?</h2>\n<p>You don't have to follow the folder structure I proposed to the T.\n<strong>Clean Architecture</strong> is very flexible, and you can experiment with it and structure it the way you like.</p>\n<p>Do you like more granularity? Create more specific projects.</p>\n<p>Do you dislike a lot of projects? Separate concerns using folders.</p>\n<p>I'm here to give you options to explore. But it's up to you to decide what's best.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/clean-architecture-folder-structure",
            "title": "How To Approach Clean Architecture Folder Structure",
            "summary": "Clean Architecture is a popular approach to structuring your .NET application. It's a layered architecture and splits into four layers: Domain, Application…",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_004.png",
            "date_modified": "2022-09-24T00:00:00.000Z",
            "date_published": "2022-09-24T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting",
            "content_html": "<p><strong>Query splitting</strong>, introduced in <strong>EF Core 5.0</strong>, lets you split a single LINQ query with <code>Include</code> statements into multiple SQL queries, one per included navigation.\nYou enable it by calling <code>AsSplitQuery</code> on a single query, or for every query with <code>UseQuerySplittingBehavior</code>.\nIt avoids the Cartesian explosion that can make a join-heavy query slow enough to time out.</p>\n<p>I recently ran into an issue with <strong>Entity Framework Core</strong>.</p>\n<p>The query I was running was constantly timing out.</p>\n<p>I tried to scale up the application server, and it didn't help.</p>\n<p>I tried to scale up the database server, and it didn't help.</p>\n<p>So how did I solve the problem?</p>\n<h2>What Was The Problem With This Query?</h2>\n<p>I'm working on an application in the e-commerce domain.\nTo be specific, it's an order management system for a kitchen cabinet manufacturer.</p>\n<p>The table that I frequently query on is the <code>Orders</code> table.\nThe <code>Order</code> can have one or more <code>LineItems</code>.\nA typical <code>Order</code> will contain 50 <code>LineItems</code>.\nAlso, <code>LineItems</code> have a table that contains the valid dimensions - <code>LineItemDimensions</code>.</p>\n<p>This is the query I was trying to run:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Orders\n    .Include(order =&gt; order.LineItems)\n    .ThenInclude(lineItem =&gt; lineItem.Dimensions)\n    .First(order =&gt; order.Id == orderId);\n</code></pre>\n<p>When EF Core converts this into SQL, this is what it will send to the database:</p>\n<pre><code class=\"language-sql\">SELECT o.*, li.*, d.*\nFROM Orders o\nLEFT JOIN LineItems li ON li.OrderId = o.Id\nLEFT JOIN LineItemDimensions d ON d.LineItemId = li.Id\nWHERE o.Id = @orderId\nORDER BY o.Id, li.Id, d.Id;\n</code></pre>\n<p>In most cases, this query will execute just fine.</p>\n<p>However, in my situation I was running into the problem of <em>Cartesian Explosion</em>.\nThis is mainly because of the join to the <code>LineItemDimensions</code> table.\nAnd this is what's causing my query to fail, and time out.</p>\n<p>So how did I solve this problem?</p>\n<h2>Query Splitting To The Rescue</h2>\n<p>With the release of <strong>EF Core 5.0</strong> we got a new feature called <strong>Query Splitting</strong>.\nThis allows us to specify that a given LINQ query should be split into multiple <code>SQL</code> queries.</p>\n<p>To use <strong>Query Splitting</strong>, all you need to do is call the <code>AsSplitQuery</code> method:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Orders\n    .Include(order =&gt; order.LineItems)\n    .ThenInclude(lineItem =&gt; lineItem.Dimensions)\n    .AsSplitQuery()\n    .First(order =&gt; order.Id == orderId);\n</code></pre>\n<p>In this case, EF Core will generate the following SQL queries:</p>\n<pre><code class=\"language-sql\">SELECT o.*\nFROM Orders o\nWHERE o.Id = @orderId;\n\nSELECT li.*\nFROM LineItems li\nJOIN Orders o ON li.OrderId = o.Id\nWHERE o.Id = @orderId;\n\nSELECT d.*\nFROM LineItemDimensions d\nJOIN LineItems li ON d.LineItemId = li.Id\nJOIN Orders o ON li.OrderId = o.Id\nWHERE o.Id = @orderId;\n</code></pre>\n<p>Notice that for each <code>Include</code> statement we have a separate <code>SQL</code> query.\nThe benefit here is that we are not duplicating data when fetching from the database,\nas we were in the previous case.</p>\n<h2>Turning On Query Splitting For All Queries</h2>\n<p>You can enable <strong>Query Splitting</strong> at the database context level.\nWhen configuring your database context you need to call the <code>UseQuerySplittingBehavior</code> method:</p>\n<pre><code class=\"language-csharp\">services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;\n    options.UseSqlServer(\n        &quot;CONNECTION_STRING&quot;,\n        o =&gt; o.UseQuerySplittingBehavior(\n            QuerySplittingBehavior.SplitQuery)));\n</code></pre>\n<p>This will cause all queries that EF Core generates to be split queries.\nTo revert back to a single query, you need to call the <code>AsSingleQuery</code> method:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Orders\n    .Include(o =&gt; o.LineItems)\n    .ThenInclude(li =&gt; li.Dimensions)\n    .AsSingleQuery()\n    .First(o =&gt; o.Id == orderId);\n</code></pre>\n<h2>What You Should Know About Query Splitting</h2>\n<p>Although query splitting is an excellent addition to EF Core, there are a few things you need to be aware of.</p>\n<p>There is no consistency guarantee for multiple SQL queries.\nYou may run into a problem if you have a concurrent update going through\nat the same time when you query your data.\nTo mitigate this, you can wrap the queries inside of a transaction, but this will only introduce performance issues elsewhere.</p>\n<p>Each query will require one network round trip. This can degrade performance if your latency to the database is high.</p>\n<p>Now that you are armed with this knowledge, go and <a href=\"https://milanjovanovic.tech/blog/ef-core-performance-guide\"><strong>make your EF queries faster</strong></a>!</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting",
            "title": "How To Improve Performance With EF Core Query Splitting",
            "summary": "I recently ran into an issue with Entity Framework Core. The query I was running was constantly timing out.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_003.png",
            "date_modified": "2022-09-17T00:00:00.000Z",
            "date_published": "2022-09-17T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/records-anonymous-types-non-destructive-mutation",
            "content_html": "<p>Non-destructive mutation means producing a modified copy of an object instead of changing it.\nThe <code>with</code> expression, introduced in C# 9, creates a new record instance with only the specified properties changed, while the original remains unchanged.\nIt works with anonymous types too.</p>\n<p>Today, I'm going to share some fascinating things you can do with records and anonymous types.\nI will introduce you to the concept of non-destructive mutation.\nAnd I will talk about when and why we might want to use this C# language feature.</p>\n<h2>What Is a Record?</h2>\n<p>With <strong>C# 9</strong> we can use <a href=\"https://milanjovanovic.tech/blog/csharp-records-when-how\"><strong>records</strong></a> that are a new reference type.\n<strong>C# 10</strong> introduced record structs so that you can define records as value types.\nRecords are distinct from classes in that record types use value-based equality.</p>\n<p>Let's see how we would define a <code>record</code>:</p>\n<pre><code class=\"language-csharp\">public record Food(string Name, double Price);\n</code></pre>\n<p>This way of declaring a <code>record</code> is called a positional record.\nThe constructor we have defined here is called the <strong>primary constructor</strong>.</p>\n<p>The <code>Name</code> and <code>Price</code> properties are init only properties.\nThis means they can only be set in the constructor or using a property initializer.</p>\n<p>Since our properties are init only, is there any way to change their value?</p>\n<h2>Non-Destructive Mutation Using The With Expression</h2>\n<p>We said we can't modify the properties of our <code>record</code>, because the properties are init only.\nHowever, we can use the <code>with</code> expression (introduced in <strong>C# 9</strong>) to create a new instance\nof our record with modified values.</p>\n<p>Let's see how we would use the <code>with</code> expression:</p>\n<pre><code class=\"language-csharp\">var banana = new Food(&quot;🍌&quot;, 1.95);\n\nvar bananaOnSale = banana with\n{\n    Price = 0.99\n};\n</code></pre>\n<p>It's important to highlight two things here:</p>\n<ul>\n<li>The original banana instance remains unchanged</li>\n<li>The <code>with</code> expression creates a new record instance with only the <code>Price</code> property modified</li>\n</ul>\n<p>I mentioned Anonymous Types in the title, so let me show you something interesting you can do with them.</p>\n<h2>Anonymous Types And Non-Destructive Mutation</h2>\n<p>Did you know that you can use the <code>with</code> expression with anonymous types?</p>\n<p>Just a reminder that the <code>with</code> expression is available from <strong>C# 9</strong> and later.</p>\n<p>Let's create an anonymous type:</p>\n<pre><code class=\"language-csharp\">var apple = new\n{\n    Name = &quot;🍎&quot;,\n    Price = 1.21\n};\n</code></pre>\n<p>This is how we can modify it using the with expression:</p>\n<pre><code class=\"language-csharp\">var orange = apple with\n{\n    Name = &quot;🍊&quot;\n};\n</code></pre>\n<p>And again the same rules apply:</p>\n<ul>\n<li>The original apple instance remains unchanged</li>\n<li>The with expression creates a new anonymous type instance with only the Name property modified</li>\n</ul>\n<p>I found this feature useful in LINQ method chains.</p>\n<p>For example, loading an anonymous type from the database where some properties have a default value.\nYou can then use this feature to calculate the values for these properties in memory.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/records-anonymous-types-non-destructive-mutation",
            "title": "Records, Anonymous Types, and Non-Destructive Mutation",
            "summary": "Today, I'm going to share some fascinating things you can do with records and anonymous types. I will introduce you to the concept of non-destructive mutation.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_002.png",
            "date_modified": "2022-09-10T00:00:00.000Z",
            "date_published": "2022-09-10T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        },
        {
            "id": "https://milanjovanovic.tech/blog/why-i-write-tall-linq-queries",
            "content_html": "<p>A tall LINQ query puts each method call in the chain on its own line, so the expression grows vertically instead of stretching across the screen.\nEach step becomes easier to read, and easier to follow into the next one in the chain.</p>\n<h2>Wishing You a Warm Welcome</h2>\n<p>First, I want to welcome you to the first edition of <strong>Milan's .NET Weekly</strong> newsletter.</p>\n<p>I hope that this newsletter can become a positive force in the .NET community.\nTo bring many of us together so that we can all continue learning and improving.</p>\n<p>With that out of the way, let's get into .NET!</p>\n<h2>The Problem With Wide LINQ</h2>\n<p>Let's consider the following LINQ expression from a code style perspective.</p>\n<p>I call this a wide LINQ expression because it stretches horizontally across the entire screen.</p>\n<pre><code class=\"language-csharp\">dbContext.Animals.Where(animal =&gt; animal.HasBigEars)\n    .OrderBy(animal =&gt; animal.IsDangerous).Select(\n        animal =&gt; (animal.Id, animal.Name)).ToList();\n</code></pre>\n<ul>\n<li>It is difficult to read.</li>\n<li>It is difficult to reason about.</li>\n<li>It is difficult to extend or maintain.</li>\n</ul>\n<p>To improve this, I created a simple rule that you can follow:</p>\n<blockquote>\n<p>When writing LINQ, try to go tall, not wide.</p>\n</blockquote>\n<h2>How to Write Tall LINQ</h2>\n<p>So how do we write tall LINQ expressions?</p>\n<p>I'm going to rewrite the previous expression, to improve it.</p>\n<p>Try to follow the <em>one dot per line rule</em>:</p>\n<pre><code class=\"language-csharp\">dbContext\n    .Animals\n    .Where(animal =&gt; animal.HasBigEars)\n    .OrderBy(animal =&gt; animal.IsDangerous)\n    .Select(animal =&gt; (animal.Id, animal.Name))\n    .ToList();\n</code></pre>\n<p>Is the new version easier to read? <strong>Yes</strong>, very much so.</p>\n<p>It is easier to understand what each expression does,\nand how it feeds into the next one in the chain.</p>\n<p>If you are working in a team, try to propose this as a coding standard (if it isn't one already).<br>\nYou will see that over time this will make a noticeable difference.</p>\n<hr>\n",
            "url": "https://milanjovanovic.tech/blog/why-i-write-tall-linq-queries",
            "title": "Why I Write My LINQ Queries Tall, Not Wide",
            "summary": "In this newsletter, I'll show you how you can write tall LINQ queries to improve readability and make your code easier to maintain.",
            "image": "https://milanjovanovic.tech/blog-covers/mnw_001.png",
            "date_modified": "2022-09-03T00:00:00.000Z",
            "date_published": "2022-09-03T00:00:00.000Z",
            "author": {
                "name": "Milan Jovanović",
                "url": "https://milanjovanovic.tech"
            }
        }
    ]
}