<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Michael Dugmore</title>
        <link>https://michael-dugmore.pages.dev/</link>
        <description>Recent content on Michael Dugmore</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-gb</language><atom:link href="https://michael-dugmore.pages.dev/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Vibes ≠ Shipping</title>
        <link>https://michael-dugmore.pages.dev/p/vibe-coding-meets-the-bank/</link>
        <pubDate>Sat, 31 Jan 2026 09:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/vibe-coding-meets-the-bank/</guid>
        <description>&lt;blockquote&gt;
&lt;p&gt;Personal views only (not my employer&amp;rsquo;s). This is a snapshot of my current thinking as we all learn AI - examples are from personal projects and experiments on non-critical systems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-idea&#34;&gt;The Idea
&lt;/h2&gt;&lt;p&gt;Vibe coding (which I dont actually like the term for all Agentic Coding, but it is what people use now) used to feel like a novelty. You describe what you want, the model fills in the syntax, and you ship something before your coffee gets cold.&lt;/p&gt;
&lt;p&gt;Now it just feels normal. My feed is full of weekend build threads and the &amp;ldquo;coding is dead&amp;rdquo; crowd is never far behind.&lt;/p&gt;
&lt;p&gt;I spent the holidays building that family planner I mentioned before. It&amp;rsquo;s mostly just a glorified way for us to argue about whose turn it is to take the bins out, but it’s &lt;em&gt;ours&lt;/em&gt;. I spent two weeks vibe coding with LLM agents and shipping features in minutes. It was fun, right up until I opened my laptop on January 2nd and got that first production alert from the day job.&lt;/p&gt;
&lt;h2 id=&#34;the-gap&#34;&gt;The gap
&lt;/h2&gt;&lt;p&gt;Over the break, the cost of being wrong was basically zero. If the LLM generated something weird, I just rolled back and tried again. Explore, discard, rewrite.&lt;/p&gt;
&lt;p&gt;Inside a bank, the friction is the point. We are building systems that have to survive auditors, 3am callouts, and days when the market goes sideways. When a system moves money, &amp;ldquo;move fast and break things&amp;rdquo; is basically just professional negligence.&lt;/p&gt;
&lt;p&gt;I &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-rules-and-weekdoc/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;vibe-coded the planner&lt;/a&gt; over the break and the code was fine for the most part. The interesting bit was what I had to stop the tool from adding. It generated this elaborate caching layer I never asked for. I only noticed it when a to-do item showed up twice in the same list. It was the same task and the same timestamp, but the IDs were different.&lt;/p&gt;
&lt;p&gt;The model just decided I needed caching. Because I was in &amp;ldquo;vibe mode,&amp;rdquo; I merged the PR without really looking at the implementation.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s fine for a chore list but in a bank, &amp;ldquo;the model decided&amp;rdquo; won&amp;rsquo;t get you through an audit.&lt;/p&gt;
&lt;p&gt;I ended up writing a &amp;ldquo;constitution&amp;rdquo; for the agent. These were guardrails like the WeekDoc rule, where we use one JSON document per week and replace it atomically. Without constraints, these tools breed complexity like they&amp;rsquo;re competing for a prize. I had to be the one saying &amp;ldquo;no, simpler.&amp;rdquo; The AI wanted tables and relations; I wanted something I could debug at 11pm when the data looks wrong.&lt;/p&gt;
&lt;h2 id=&#34;a-meta-interlude-the-generic-trap&#34;&gt;A meta-interlude: The generic trap
&lt;/h2&gt;&lt;p&gt;I actually hit this exact wall trying to write this blog post. I had these rough ideas piling up for weeks and thought, &amp;ldquo;let&amp;rsquo;s just vibe-code the article.&amp;rdquo; I fed the notes into ChatGPT, Gemini, and Claude to see what would happen.&lt;/p&gt;
&lt;p&gt;The results were&amp;hellip; fine? Actually, they were too fine. Perfectly polished and totally empty. It read like generic thought-leader sludge. I realized that without being hyper-specific, you just get the median output of the internet.&lt;/p&gt;
&lt;p&gt;So I didn&amp;rsquo;t save any time. I ended up rewriting most of this from scratch to get it to sound like a human. I did still use the LLMs to fix my terrible grammar and tighten the flow, so if this reads well and is still a little polished and &amp;ldquo;thought-leaderish&amp;rdquo; credit to them, but I had to drive the bus.&lt;/p&gt;
&lt;p&gt;The back-and-forth was actually the useful part. Arguing with the LLM&amp;rsquo;s about the narrative helped me figure out what I actually wanted to say. It echoes the coding loop exactly: you hardly ever accept the first block of code. You refine, reject, and tweak until the mental model in your head matches the thing on the screen.&lt;/p&gt;
&lt;h2 id=&#34;a-different-kind-of-lock-in&#34;&gt;A different kind of lock-in
&lt;/h2&gt;&lt;p&gt;When I &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/vendor-lockin-ai-freedom/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;wrote before about AI and vendor lock-in&lt;/a&gt;, I was thinking about the economics. Cheaper custom software and smaller teams doing more. That&amp;rsquo;s real, but I think I missed the risk that actually matters day-to-day.&lt;/p&gt;
&lt;p&gt;I’m becoming less worried about platform lock-in and much more worried about &lt;strong&gt;intent lock-in&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I keep catching myself accepting code I don&amp;rsquo;t fully understand. The &amp;ldquo;why&amp;rdquo; behind the system lives in a chat window I can&amp;rsquo;t easily replay or share with the team. I’m not really building anymore it feels like I’m just curating a series of fortunate accidents.&lt;/p&gt;
&lt;h2 id=&#34;craft-vs-outcomes&#34;&gt;Craft vs. Outcomes
&lt;/h2&gt;&lt;p&gt;When this comes up in team discussions and online, I notice two reactions.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s the engineer who loves the craft. They want clean abstractions and they want to know what’s happening under the hood. For them, agentic coding feels like skipping the part where you actually understand the system. I used to think that was just being a Luddite, but now I think it’s just scar tissue. When something breaks at 2am and money is moving, you need a mental model of the system. You don&amp;rsquo;t need a stack of diffs that passed review because they looked plausible.&lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s the engineer motivated by outcomes. Ship, validate, move on. They have adopted these tools with a speed that is honestly a bit scary. They aren&amp;rsquo;t worried about losing the craft; they&amp;rsquo;re worried about what the job becomes when the productivity curve bends that hard. If one person can do in a day what used to take a team a week, what happens to the team?&lt;/p&gt;
&lt;p&gt;I flip between these modes constantly. Context usually decides which instinct is rational, but the trick is knowing which context you are actually in. I’m starting to suspect that framing is a bit too clean anyway. Maybe it&amp;rsquo;s just one instinct (get the thing working) filtered through how many times you’ve been burned by a system you didn&amp;rsquo;t understand.&lt;/p&gt;
&lt;h2 id=&#34;normalisation-of-deviance&#34;&gt;Normalisation of deviance
&lt;/h2&gt;&lt;p&gt;Johann Rehberger wrote about the &lt;a class=&#34;link&#34; href=&#34;https://embracethered.com/blog/posts/2025/the-normalization-of-deviance-in-ai/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;“normalisation of deviance” in AI&lt;/a&gt;. It&amp;rsquo;s a concept from the Challenger disaster. If you get away with cutting a corner often enough, you stop seeing it as a corner. It just becomes how you do things.&lt;/p&gt;
&lt;p&gt;The LLM generates something that looks right, the tests pass, and it deploys without issues. Do that enough times and your brain learns the tool is competent. You stop doing the work of being competent yourself.&lt;/p&gt;
&lt;p&gt;I caught myself doing this last week. A 400-line PR came through from an agent. I skimmed it, saw the structure looked okay, and left a &amp;ldquo;looks good&amp;rdquo; comment. (This was not on a critical system, more as an expirement on the AI workflows). The next day I realized I hadn&amp;rsquo;t actually checked the error handling pattern. I trusted it because it looked like the kind of code the tool usually generates, and that code is usually fine.&lt;/p&gt;
&lt;p&gt;Usually.&lt;/p&gt;
&lt;p&gt;This is the &amp;ldquo;maintenance dose&amp;rdquo; problem &lt;a class=&#34;link&#34; href=&#34;https://news.ycombinator.com/item?id=46809846#46837226&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;I saw on Hacker News&lt;/a&gt;. If you only do 5% of the thinking, your judgment starts to atrophy. The failures aren&amp;rsquo;t loud; they&amp;rsquo;re subtle. A strange dependency choice or a slightly-off concurrency assumption. A security pattern you wouldn&amp;rsquo;t naturally reach for.&lt;/p&gt;
&lt;h2 id=&#34;is-this-an-ai-problem-or-a-speed-problem&#34;&gt;Is this an AI problem or a speed problem?
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s a concrete example I’ve been chewing on. Imagine an Anti-Money Laundering rule in a banks Transaction Monitoring system that flags any transaction over £10,000 for review.&lt;/p&gt;
&lt;p&gt;The code looks fine, but the check runs against the integer portion of the amount. £9,999.99 slips right through. In the world of financial crime, that&amp;rsquo;s a structuring exploit.&lt;/p&gt;
&lt;p&gt;The requirement said &amp;ldquo;over £10,000.&amp;rdquo; Nobody captured why the boundary matters. And because the tooling makes the whole thing take an afternoon instead of two weeks, the team never hits a natural moment where someone stops and asks what the failure mode is.&lt;/p&gt;
&lt;p&gt;Maybe I&amp;rsquo;m over-thinking on this. Is it an AI problem, or is it just what happens when we move too fast? I don&amp;rsquo;t know. The lines are starting to feel very blurry.&lt;/p&gt;
&lt;h2 id=&#34;the-difference-between-a-wish-and-intent&#34;&gt;The difference between a wish and intent
&lt;/h2&gt;&lt;p&gt;I think the real shift occurring is abstraction more than automation. We used to specify how systems work because we had to own the implementation details. Now we are more and more specifying what we want and the &amp;ldquo;how&amp;rdquo; is negotiated between prompts and models.&lt;/p&gt;
&lt;p&gt;So where does intent live?&lt;/p&gt;
&lt;p&gt;A prompt that says &amp;ldquo;build a service to validate account balances&amp;rdquo; is really just a wish. Intent is closer to the messy stuff: &lt;em&gt;validate balances, fail fast if the ledger times out after 200ms, log errors with PII scrubbed, alert the on-call if error rate exceeds 1%.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One is asking for code; the other is describing a system. If the only place your architectural decisions exist is inside a proprietary context window, you are just renting it. And landlords can change the terms or lose the receipts anytime they want.&lt;/p&gt;
&lt;h2 id=&#34;what-im-actually-doing&#34;&gt;What I&amp;rsquo;m actually doing
&lt;/h2&gt;&lt;p&gt;I don&amp;rsquo;t buy the future where we vibe-code straight into production, at least not in regulated industries (anytime soon). But simply banning the tools feels like denial with extra steps.&lt;/p&gt;
&lt;p&gt;So I’ve been trying a few things. I try use AI as a drafter, not an author. It handles the scaffolding and the glue code, the boring stuff that doesn&amp;rsquo;t need deep domain knowledge. This works well until the agent gets stuck in a loop and I have to take over anyway. (This applies to writing too, the AI drafts, but I have to do the heavy lifting to make it real).&lt;/p&gt;
&lt;p&gt;I’m also trying to treat intent as a first-class artefact. We try to write out the plans and the constraints before letting the agent loose. This definitely goes against that move fast feeling, but I will sleep better at night if we have properly documented specs/requirements that the agents have developed against so we can fall back on that. Mixed results so far.&lt;/p&gt;
&lt;p&gt;The goal is to make it replayable. If an agent produced something important, I want to know how it got there. I haven&amp;rsquo;t figured out how to do this at scale without it feeling like homework, so I&amp;rsquo;m still looking for a better way. The other obvious one is the traditional TDD (Test Driven Development) way, which if done correctly will also have all the intent up front, this though is difficult if your teams or engineers are not used to doing full proper TDD, as it is a mindset change.&lt;/p&gt;
&lt;p&gt;There is also various spec driven development techniques that people and teams are trying out and coding agents are starting to incorporate, but I have not landed on the one true way yet.&lt;/p&gt;
&lt;p&gt;I really liked the &lt;a class=&#34;link&#34; href=&#34;https://somehowmanage.com/2026/01/22/a-step-behind-the-bleeding-edge-monarchs-philosophy-on-ai-in-dev/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&amp;ldquo;step behind the bleeding edge&amp;rdquo; framing from Monarch Money&lt;/a&gt;. You explore the frontier so you understand what is coming, but you adopt it deliberately when you can wrap it in a process you can actually evidence.&lt;/p&gt;
&lt;h2 id=&#34;monday-morning&#34;&gt;Monday morning
&lt;/h2&gt;&lt;p&gt;I’m still figuring out the balance. Some days I’m convinced I’m right about this, I feel sure about intent lock-in and the need for durable ownership. Other days I watch a demo of the latest model and wonder if I’m just overthinking it. Maybe the tools just get better and the problem solves itself?&lt;/p&gt;
&lt;p&gt;Probably not, though.&lt;/p&gt;
&lt;p&gt;Regulation isn&amp;rsquo;t going away. Neither are incidents, audit questions, or the slow grind of maintaining systems that handle real money and real risk. The tools are getting better faster than our instincts can keep up.&lt;/p&gt;
&lt;p&gt;The job is making sure intent keeps pace. When we approve a PR, we shouldn&amp;rsquo;t just be approving a diff. We are approving a system we&amp;rsquo;ll be willing to own later.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the bit I can&amp;rsquo;t stop thinking about. At least, that&amp;rsquo;s the best frame I have for it right now.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Books I Read in 2025</title>
        <link>https://michael-dugmore.pages.dev/p/books-2025/</link>
        <pubDate>Sun, 04 Jan 2026 09:12:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/books-2025/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/books-2025/books-cover.png" alt="Featured image of post Books I Read in 2025" /&gt;&lt;p&gt;I didn’t really keep notes while I was reading these. This is basically me looking back and going, “oh yeah… that one”, and trying to remember what actually stuck, I have also missed a few others, which I will update if I get the time.&lt;/p&gt;
&lt;p&gt;Not a massively curated list, but there’s a theme (as usual): health, habits, money, tech, and why things work the way they do.&lt;/p&gt;
&lt;h2 id=&#34;exercised-the-science-of-physical-activity-rest-and-health---daniel-lieberman&#34;&gt;Exercised: The Science of Physical Activity, Rest and Health - Daniel Lieberman
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.amazon.co.uk/Exercised-Science-Physical-Activity-Health/dp/0241309271&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/exercised.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/exercised_hu34c6b1d5edb54b1d9eab726dd1d9e775_15907_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/exercised_hu34c6b1d5edb54b1d9eab726dd1d9e775_15907_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Exercised&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Not a fitness book in the usual “here’s your plan” sense. It’s more about why humans move at all, and how “exercise” as we think about it is a pretty modern concept.&lt;/p&gt;
&lt;p&gt;It made exercise feel less loaded. Movement matters, but it doesn’t have to look a certain way to count. I didn’t suddenly become a new person, but it did quietly reset how I think about being active.&lt;/p&gt;
&lt;h2 id=&#34;burn---herman-pontzer&#34;&gt;Burn - Herman Pontzer
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.penguin.co.uk/books/312914/burn-by-pontzer-herman/9780141990170&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/burn.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/burn_huc4ae9d2c60fa604bf1a32208c97655e9_18040_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/burn_huc4ae9d2c60fa604bf1a32208c97655e9_18040_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Burn&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Probably my favourite thing I read this year. It explains metabolism in a way that actually makes sense, without turning into a diet plan or trying to sell you a system.&lt;/p&gt;
&lt;p&gt;The main idea (as I understood it anyway) is that the body adapts more than we assume. You don’t just keep burning more and more calories forever by exercising more. Once that clicks, a lot of the usual health advice suddenly looks a bit wonky.&lt;/p&gt;
&lt;h2 id=&#34;atomic-habits---james-clear&#34;&gt;Atomic Habits - James Clear
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://jamesclear.com/atomic-habits&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/atomic-habits.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/atomic-habits_huf0bf0e0196166c8be71c1efcb8e0a9c8_31766_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/atomic-habits_huf0bf0e0196166c8be71c1efcb8e0a9c8_31766_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Atomic Habits&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I nearly skipped this because I’m pretty tired of productivity books, but I’m glad I didn’t. It’s straightforward and practical without being preachy.&lt;/p&gt;
&lt;p&gt;The focus on small changes and just making the “good” thing easier than the “bad” thing felt realistic. I didn’t overhaul my life (thankfully). It mainly made me notice what I’m already doing on autopilot.&lt;/p&gt;
&lt;h2 id=&#34;same-as-ever---morgan-housel&#34;&gt;Same as Ever - Morgan Housel
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://harriman-house.com/authors/morgan-housel/same-as-ever/9781804090633&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/same-as-ever.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/same-as-ever_hudee1f7e1aa47b54b6905d68479ff8f72_27549_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/same-as-ever_hudee1f7e1aa47b54b6905d68479ff8f72_27549_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Same as Ever&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This one was just enjoyable. Short chapters that are easy to dip in and out of and nothing too heavy.&lt;/p&gt;
&lt;p&gt;The point is basically that while the world changes constantly, people don’t (at least not in the ways that matter). Fear, optimism, bad incentives, overconfidence all of that has been around forever. I found it oddly calming, especially with how noisy everything else feels.&lt;/p&gt;
&lt;h2 id=&#34;enshittification-why-everything-suddenly-got-worse-and-what-to-do-about-it---corey-doctorow&#34;&gt;Enshittification: Why Everything Suddenly Got Worse and What To Do About It - Corey Doctorow
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.amazon.co.uk/Enshittification-Everything-Suddenly-Worse-About/dp/1836742223&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/enshit.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/enshit_hucb552049acfb2cf4991bd30276668843_22113_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/enshit_hucb552049acfb2cf4991bd30276668843_22113_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Enshittification&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a book from an Essay Corey Doctorow, it put a name to something I’ve been noticing for years. Platforms start out useful, then slowly get worse as incentives shift away from users.&lt;/p&gt;
&lt;p&gt;It’s not exactly cheerful, but it is clarifying. Once you’ve seen the pattern properly, it’s hard not to notice it everywhere.&lt;/p&gt;
&lt;h2 id=&#34;project-hail-mary-audiobook-re-listen&#34;&gt;Project Hail Mary (audiobook, re-listen?)
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.audible.com/pd/Project-Hail-Mary-Audiobook/B08G9PRS1K&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;img src=&#34;https://michael-dugmore.pages.dev/p/books-2025/hail-mary.jpg&#34;
	width=&#34;280&#34;
	height=&#34;420&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/books-2025/hail-mary_hu4b130db91b29170cecd658414147c23e_32296_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/books-2025/hail-mary_hu4b130db91b29170cecd658414147c23e_32296_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Project Hail Mary&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;66&#34;
		data-flex-basis=&#34;160px&#34;
	
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I read this a few years ago and loved it, almost as much as &lt;strong&gt;The Martian&lt;/strong&gt;. I didn’t enjoy &lt;strong&gt;Artemis&lt;/strong&gt; nearly as much, but &lt;em&gt;Project Hail Mary&lt;/em&gt; felt like a proper return to form.&lt;/p&gt;
&lt;p&gt;This year I came back to it as an audiobook after hearing how good the narration was, and yeah, it’s excellent. It also turned into a bit of a bonding thing with my son, we listened together in the evenings, a chapter or two at a time, and he was completely hooked. Seeing him get pulled into it made me enjoy it even more the second time around.&lt;/p&gt;
&lt;p&gt;That’s the lot. If you’ve read anything similar (or think I’ve missed something obvious), send it my way.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Reviving a 9-year-old Dell Latitude with Linux Mint</title>
        <link>https://michael-dugmore.pages.dev/p/linux-mint-laptop/</link>
        <pubDate>Sat, 03 Jan 2026 10:05:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/linux-mint-laptop/</guid>
        <description>&lt;p&gt;A couple of months ago I nearly replaced my personal laptop.&lt;/p&gt;
&lt;p&gt;My daily driver is a 9-year-old &lt;a class=&#34;link&#34; href=&#34;https://www.dell.com/support/home/en-us/product-support/product/latitude-12-7280-laptop/overview&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Dell Latitude 7280&lt;/a&gt;. On paper the hardware is fine, but running Windows 10 it felt dead. The fans were constantly spinning, the system was sluggish, and the battery life had dropped to a useless 30 minutes.&lt;/p&gt;
&lt;p&gt;Since the machine is too old for the official Windows 11 upgrade path, it was effectively living on borrowed time.&lt;/p&gt;
&lt;p&gt;I’ve been using macOS for work for the last five years because Apple Silicon is hard to beat, but I like the idea of being a &amp;ldquo;tech polyglot&amp;rdquo; and not relying on a single ecosystem. I wanted to see if I could salvage the Dell.&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://linuxmint.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Linux Mint&lt;/a&gt; is frequently recommended as the &amp;ldquo;just works&amp;rdquo; distro for older hardware, so I wiped the drive and installed it.&lt;/p&gt;
&lt;h2 id=&#34;the-adblocker-effect&#34;&gt;The Adblocker Effect
&lt;/h2&gt;&lt;p&gt;I’ve been running this setup for two months now, and so far it has been &amp;ldquo;boring&amp;rdquo;, which is a compliment. Nothing flashy, no ads in the menus, quick to use. Just what I want out of my machine, it feels like I own it too.&lt;/p&gt;
&lt;p&gt;My favorite description of the experience of using Windows lately feels like &lt;strong&gt;browsing the web without an adblocker&lt;/strong&gt;. It’s noisy, distracting, constantly trying to sell you things and just feels like everything is disjointed and slow.&lt;/p&gt;
&lt;p&gt;Linux Mint so far for me is the complete opposite. It’s quiet, it doesn’t try to be clever, and it stays out of the way.&lt;/p&gt;
&lt;p&gt;The hardware results were immediate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Silence:&lt;/strong&gt; The fans used to wheeze constantly, but they almost never spin up now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Battery:&lt;/strong&gt; I’m getting nearly two hours of usage, up from 30 minutes. The laptop is actually portable again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drivers:&lt;/strong&gt; This was my biggest worry, but everything (Wi-Fi, sound, display) worked out of the box.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-workflow&#34;&gt;The Workflow
&lt;/h2&gt;&lt;p&gt;I’m keeping my setup simple with mostly Firefox and &lt;a class=&#34;link&#34; href=&#34;https://code.visualstudio.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;VS Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ve been spending a lot of time building small tools with LLMs recently, like this &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/family-planner-vibe-coding-the-whole-story/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;family planner app&lt;/a&gt; I hacked together. For that workflow, a lightweight Linux environment is perfect. It feels closer to the metal than my Mac without the overhead of Windows.&lt;/p&gt;
&lt;h2 id=&#34;my-mint-setup&#34;&gt;My Mint setup
&lt;/h2&gt;&lt;p&gt;These are the highlights of the machine and install I’m writing this post on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Laptop:&lt;/strong&gt; Dell Latitude 7280&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS / DE:&lt;/strong&gt; Linux Mint 22.2 “Zara” (Ubuntu 24.04 base), Cinnamon 6.4.8 (X11)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kernel:&lt;/strong&gt; 6.14.0-37-generic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU / RAM:&lt;/strong&gt; Intel i7-7600U (2C/4T), 16GB RAM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Display:&lt;/strong&gt; 1920×1080&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; 512GB SanDisk X400 (ext4 root)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Battery health:&lt;/strong&gt; ~37.6% of original capacity remaining (about 22.5Wh now vs 60Wh design)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to grab similar details on your own Linux box, this is the one-liner I used:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;inxi -Fxz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;office-apps-and-the-real-life-test&#34;&gt;Office apps (and the “real-life” test)
&lt;/h2&gt;&lt;p&gt;I installed two office suites (and LibreOffice comes pre‑installed with Mint):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LibreOffice&lt;/strong&gt; (Writer, Calc, Impress) — &lt;a class=&#34;link&#34; href=&#34;https://www.libreoffice.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.libreoffice.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ONLYOFFICE Desktop Editors&lt;/strong&gt; — &lt;a class=&#34;link&#34; href=&#34;https://www.onlyoffice.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.onlyoffice.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UI-wise, ONLYOFFICE feels closest to Microsoft Office, especially the ribbon layout and overall visual structure. LibreOffice is still the default workhorse and feels a little more “classic Linux,” but it’s rock-solid.&lt;/p&gt;
&lt;p&gt;The best part: I got my wife to edit a document and print what she needed with zero hassle. That’s a big win on an older laptop.&lt;/p&gt;
&lt;p&gt;I haven’t tried these yet, but they’re on my radar: WPS Office, SoftMaker Office, FreeOffice, Calligra Suite, and Apache OpenOffice.&lt;/p&gt;
&lt;p&gt;I also haven’t had to do complex spreadsheets with macros or heavy Excel‑specific workflows. That might be a bigger issue for people who depend on those features.&lt;/p&gt;
&lt;h2 id=&#34;next-steps-the-dual-boot-experiment&#34;&gt;Next steps: The Dual-Boot Experiment
&lt;/h2&gt;&lt;p&gt;Because this went so well, I’m planning to dual-boot the slightly newer Windows 11 laptop my son uses.&lt;/p&gt;
&lt;p&gt;The stakes are higher there because his school relies on Microsoft Teams and Office, plus he spends a lot of time in Roblox.&lt;/p&gt;
&lt;p&gt;I recently discovered &lt;a class=&#34;link&#34; href=&#34;https://sober.vinegarhq.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Sober&lt;/a&gt;, a Flatpak wrapper for the Android version of Roblox. I’ve tested it on my Mint machine and it runs surprisingly well, which solves the biggest blocker for him.&lt;/p&gt;
&lt;p&gt;There is a philosophy aspect to this too. Watching my son’s generation use computers, I’ve noticed they treat them purely as appliances or black boxes that run apps.&lt;/p&gt;
&lt;p&gt;I know I praised Mint for &amp;ldquo;staying out of the way,&amp;rdquo; but for him I think a little friction might be healthy. If he has to drop into a terminal to fix a sound setting or tweak a config file, that’s a win. It teaches that the computer is something you can poke, prod, and change rather than a sealed unit you replace when it gets slow.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>The Day Our Container App Ghosted Application Insights</title>
        <link>https://michael-dugmore.pages.dev/p/container-app-insights-logging/</link>
        <pubDate>Fri, 02 Jan 2026 13:49:27 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/container-app-insights-logging/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/container-app-insights-logging/cover.png" alt="Featured image of post The Day Our Container App Ghosted Application Insights" /&gt;&lt;p&gt;We moved an API from Azure Functions to Azure Container Apps and expected logging to “just work.” Instead, &lt;code&gt;AppTraces&lt;/code&gt; stayed empty while &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt; filled up. This is the story of how a custom logger quietly bypassed Application Insights, how we proved the platform still worked, and what we changed to fix it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: I’ve intentionally omitted/substituted any subscription IDs, resource names, and internal identifiers. Code snippets are illustrative.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;If your app only writes to stdout/stderr, Azure Container Apps will show logs in &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;, but Application Insights won’t magically populate &lt;code&gt;AppTraces&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Our custom logging abstraction was writing directly to stdout/stderr in Container Apps, bypassing the &lt;code&gt;ILogger&lt;/code&gt; → Application Insights pipeline.&lt;/li&gt;
&lt;li&gt;Fix: route logs through &lt;code&gt;ILogger&lt;/code&gt; and/or &lt;code&gt;TelemetryClient&lt;/code&gt;, ensure the telemetry channel can persist in the container, and flush on shutdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-setup-functions-vs-container-apps&#34;&gt;The Setup: Functions vs Container Apps
&lt;/h2&gt;&lt;p&gt;In the old world, the API ran as Azure Functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application Insights was wired through the standard Functions host.&lt;/li&gt;
&lt;li&gt;Our custom logging abstraction sat on top of the platform logger, and everything showed up in &lt;code&gt;AppTraces&lt;/code&gt; as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we lifted the same workload into Azure Container Apps, we assumed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reusing the same logging abstraction would be fine.&lt;/li&gt;
&lt;li&gt;Adding the Application Insights SDK “like the docs say” would be enough: connection string in configuration, &lt;code&gt;AddApplicationInsightsTelemetry&lt;/code&gt;, and some health-check logging around startup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On paper, everything looked correct. In practice, &lt;code&gt;AppTraces&lt;/code&gt; stayed completely silent.&lt;/p&gt;
&lt;h2 id=&#34;what-we-tried-before-finding-the-real-issue&#34;&gt;What We Tried (Before Finding the Real Issue)
&lt;/h2&gt;&lt;p&gt;Once we noticed that traces were missing from &lt;code&gt;AppTraces&lt;/code&gt;, we did the usual checklist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment parity:&lt;/strong&gt; Confirmed the Application Insights resource and Log Analytics workspace were the same ones we used in Functions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration:&lt;/strong&gt; Double-checked the connection string, instrumentation key, and diagnostic settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDK wiring:&lt;/strong&gt; Added the Application Insights ASP.NET Core SDK into the container app:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AddApplicationInsightsTelemetry&lt;/code&gt; in &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ensured the connection string came from environment variables, not hard-coded config.&lt;/li&gt;
&lt;li&gt;Registered the Application Insights logging provider.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KQL sanity checks:&lt;/strong&gt; Queried both &lt;code&gt;AppTraces&lt;/code&gt; and &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt; by timestamp and correlation IDs, just in case this was a query bug.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs happily appeared in &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AppTraces&lt;/code&gt; kept pretending we didn’t exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, we had followed the documentation, the SDK was loaded, but Application Insights still wasn’t seeing our application logs.&lt;/p&gt;
&lt;h2 id=&#34;the-support-detour&#34;&gt;The Support Detour
&lt;/h2&gt;&lt;p&gt;We raised a support ticket, assuming there might be a known limitation or a misconfiguration on the platform side.&lt;/p&gt;
&lt;p&gt;The response boiled down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Container Apps write console output into &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;AppTraces&lt;/code&gt; table is driven by the Application Insights ingestion pipeline.&lt;/li&gt;
&lt;li&gt;You can’t “redirect” console logs into &lt;code&gt;AppTraces&lt;/code&gt;; the suggested approach was to query both tables in a single KQL query.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words: “This is a product limitation, you can’t get what you want.”&lt;/p&gt;
&lt;p&gt;The problem was that this answer didn’t line up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The official docs that show Container Apps + Application Insights working end-to-end.&lt;/li&gt;
&lt;li&gt;Our own experience with Functions where the same code happily wrote to &lt;code&gt;AppTraces&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So instead of accepting “not supported” as the final word, we treated it as a clue: if console logs were going only to &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;, maybe our custom logger was never actually talking to Application Insights at all.&lt;/p&gt;
&lt;h2 id=&#34;why-this-really-mattered&#34;&gt;Why This Really Mattered
&lt;/h2&gt;&lt;p&gt;Telling ourselves “we’ll just query two tables” would have been the easy answer, but it would have hurt us everywhere else:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shared KQL patterns:&lt;/strong&gt; We already had a lot of KQL built around &lt;code&gt;AppTraces&lt;/code&gt; for other workloads. Copy-pasting those queries and bolting on extra unions for a single service would make them longer and harder to reason about.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alerts:&lt;/strong&gt; Many of our alerts were driven by fairly complex queries over &lt;code&gt;AppTraces&lt;/code&gt;. Doubling every query to union in &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt; would make them even more convoluted and fragile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboards and metrics:&lt;/strong&gt; Existing dashboards were all wired to &lt;code&gt;AppTraces&lt;/code&gt; as the central source of truth. Splitting one service’s logs across tables would mean either:
&lt;ul&gt;
&lt;li&gt;Special-casing that service everywhere, or&lt;/li&gt;
&lt;li&gt;Rewriting dashboards to union across tables for all services, even those that didn’t need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single, consistent telemetry story: “if it’s an application trace, it lives in &lt;code&gt;AppTraces&lt;/code&gt;.”&lt;/li&gt;
&lt;li&gt;To avoid polluting every query and alert with table-awareness for one misbehaving service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, fixing the pipeline at the source was far cheaper than living with permanently more complex KQL.&lt;/p&gt;
&lt;h2 id=&#34;what-actually-went-wrong&#34;&gt;What Actually Went Wrong
&lt;/h2&gt;&lt;p&gt;The real issue turned out to be our own abstraction, not the platform.&lt;/p&gt;
&lt;p&gt;We got there in two parallel ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A developer was manually tracing the flow of a request, checking where logging calls originated and which services were registered in dependency injection.&lt;/li&gt;
&lt;li&gt;In parallel, we asked an AI coding assistant to analyse the codebase and the problem description. With the broader context of “&lt;code&gt;AppTraces&lt;/code&gt; is empty but console logs exist,” it quickly flagged one suspicious area: a custom logging service that wrote directly to stdout/stderr.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking more closely at that code, the pattern became obvious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We had a &lt;strong&gt;custom logging service&lt;/strong&gt; that wrapped the platform logger.&lt;/li&gt;
&lt;li&gt;In the Functions world, this wrapper ultimately still called &lt;code&gt;ILogger&lt;/code&gt;, which the host wired to Application Insights.&lt;/li&gt;
&lt;li&gt;In Container Apps, we reused the same abstraction but changed the implementation to write directly to stdout/stderr.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That subtle change meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs still “worked” in the sense that they appeared in &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;But they completely bypassed the &lt;code&gt;ILogger&lt;/code&gt; → Application Insights pipeline.&lt;/li&gt;
&lt;li&gt;So even though the SDK was installed and configured correctly, Application Insights never saw our logs, and &lt;code&gt;AppTraces&lt;/code&gt; remained empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once we viewed the system as “two independent pipelines” it made sense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Console pipeline:&lt;/strong&gt; stdout/stderr → &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App Insights pipeline:&lt;/strong&gt; &lt;code&gt;ILogger&lt;/code&gt; / &lt;code&gt;TelemetryClient&lt;/code&gt; → &lt;code&gt;AppTraces&lt;/code&gt; (and other AI tables).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our custom logger had effectively unplugged itself from the second pipeline, and the coding agent helped us see that faster than we probably would have on our own.&lt;/p&gt;
&lt;h2 id=&#34;the-fix&#34;&gt;The Fix
&lt;/h2&gt;&lt;p&gt;We kept the custom logging abstraction but made it Application Insights–aware and ensured the SDK could persist telemetry inside a Linux container.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Send scopes to &lt;code&gt;ILogger&lt;/code&gt; and &lt;code&gt;TelemetryClient&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build a scoped property bag once, use &lt;code&gt;logger.BeginScope(properties)&lt;/code&gt; for structured logs, and also push the same properties into &lt;code&gt;TelemetryClient.Context.GlobalProperties&lt;/code&gt; for telemetry correlation.&lt;/li&gt;
&lt;li&gt;Keep disposal safe: restore previous global properties and remove any extras you added.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IDisposable&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BeginLogScope&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ILogger&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TelemetryClient&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;telemetryClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;properties&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BuildLogProperties&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;loggerScope&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;BeginScope&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;properties&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;telemetryScope&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TelemetryScope&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;telemetryClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;properties&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CompositeDisposable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;loggerScope&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;telemetryScope&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wire Application Insights explicitly in &lt;code&gt;Program.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Give the telemetry channel a writable folder (the default path isn’t writable in many container images).&lt;/li&gt;
&lt;li&gt;Disable adaptive sampling if you need deterministic logging while debugging.&lt;/li&gt;
&lt;li&gt;Register flush on shutdown so buffered telemetry reaches &lt;code&gt;AppTraces&lt;/code&gt; before the container stops.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cachePath&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Combine&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetTempPath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;appinsights-cache&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CreateDirectory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cachePath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddSingleton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ITelemetryChannel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ServerTelemetryChannel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StorageFolder&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cachePath&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddApplicationInsightsTelemetry&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ConnectionString&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;APPLICATIONINSIGHTS_CONNECTION_STRING&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EnableAdaptiveSampling&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EnableDependencyTrackingTelemetryModule&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddLogging&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;b&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddApplicationInsights&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Lifetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ApplicationStopping&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Register&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;telemetryClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Flush&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;telemetryChannel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Flush&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Thread&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TimeSpan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FromSeconds&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Turn on SDK diagnostics while debugging&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting &lt;code&gt;APPLICATIONINSIGHTS_DIAGNOSTICS_LOG_LEVEL=Verbose&lt;/code&gt; made it obvious that nothing was reaching the ingestion pipeline until the &lt;code&gt;TelemetryClient&lt;/code&gt; hookup was added.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;bonus-improvements-we-would-have-missed&#34;&gt;Bonus: Improvements We Would Have Missed
&lt;/h2&gt;&lt;p&gt;The coding agent didn’t just find the custom logger problem; it also highlighted a few things we hadn’t fully appreciated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Telemetry channel storage in Linux containers:&lt;/strong&gt; The default &lt;code&gt;ServerTelemetryChannel&lt;/code&gt; uses on-disk storage. In many Linux container images, the default path isn’t writable, so the channel quietly disables persistence. Explicitly providing a writable &lt;code&gt;StorageFolder&lt;/code&gt; keeps buffering working instead of silently failing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flushing on shutdown:&lt;/strong&gt; Without a call to &lt;code&gt;telemetryClient.Flush()&lt;/code&gt; (and a brief wait) in an &lt;code&gt;ApplicationStopping&lt;/code&gt; handler, short-lived containers can exit before telemetry is sent. Adding an explicit flush ensures traces actually leave the container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency tracking for correlation:&lt;/strong&gt; Disabling modules like &lt;code&gt;DependencyTrackingTelemetryModule&lt;/code&gt; stops HTTP client spans from reaching Application Insights. That means you lose end-to-end correlation between requests, dependencies, and traces in &lt;code&gt;AppTraces&lt;/code&gt;/&lt;code&gt;AppRequests&lt;/code&gt;. Re-enabling dependency tracking restored that visibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those suggestions improved more than just this one service—they were a useful checklist for other containerised workloads using Application Insights.&lt;/p&gt;
&lt;h2 id=&#34;proof-it-worked&#34;&gt;Proof It Worked
&lt;/h2&gt;&lt;p&gt;Once the custom logger was integrated with both &lt;code&gt;ILogger&lt;/code&gt; and &lt;code&gt;TelemetryClient&lt;/code&gt;, things changed immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The same log messages appeared in &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;ContainerAppConsoleLogs_CL&lt;/code&gt; and &lt;code&gt;AppTraces&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;KQL queries like &lt;code&gt;AppTraces | where Message contains &amp;quot;&amp;lt;sample text&amp;gt;&amp;quot;&lt;/code&gt; started returning results.&lt;/li&gt;
&lt;li&gt;End-to-end correlation across HTTP requests, dependencies, and custom traces started behaving the way the docs promised.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At that point, we had a clear picture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The platform did support Application Insights in Container Apps.&lt;/li&gt;
&lt;li&gt;Our own custom logger had been bypassing the pipeline the whole time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;takeaways-for-container-apps--app-insights&#34;&gt;Takeaways for Container Apps + App Insights
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Console output alone won’t light up &lt;code&gt;AppTraces&lt;/code&gt;; you need the Application Insights logger provider or &lt;code&gt;TelemetryClient&lt;/code&gt; in the path.&lt;/li&gt;
&lt;li&gt;Custom logging abstractions should decorate, not replace, the platform logger. If your abstraction writes straight to stdout/stderr, Application Insights will never see it.&lt;/li&gt;
&lt;li&gt;In containers, configure a writable telemetry channel and flush on shutdown so you don’t lose buffered telemetry when the container stops.&lt;/li&gt;
&lt;li&gt;When support says “not supported,” it might mean “not supported the way you’re currently doing it.” A minimal repro with the SDK and a very simple logger is often the fastest way to prove (or disprove) that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re migrating from Functions to Container Apps and &lt;code&gt;AppTraces&lt;/code&gt; is silent, start by looking for any custom logger that skips &lt;code&gt;ILogger&lt;/code&gt;/&lt;code&gt;TelemetryClient&lt;/code&gt;. Plug it into the App Insights pipeline, give the channel somewhere to write, flush on exit, and your traces should come back to life.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>I Built a Family Planner We Actually Use (and It Costs £0/month)</title>
        <link>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/</link>
        <pubDate>Wed, 31 Dec 2025 12:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/overview-top-image.png" alt="Featured image of post I Built a Family Planner We Actually Use (and It Costs £0/month)" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;LLM Agents wrote the code and the bugs. The ideas and most of the wording in this blog series are mine, but edited by LLMs because I am an engineer, not a copywriter. I’ve reviewed it to ensure it sounds like me, but if you spot weird phrasing, blame the robot.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Over the Christmas break I built a simple weekly family planner for my partner, my son, and me.&lt;/p&gt;
&lt;p&gt;I wanted to solve for the basics that kept slipping: PE kit, clubs, bin day changes, “what are we doing next Saturday?”, and the scavenger hunt through calendars, WhatsApp threads, and school emails.&lt;/p&gt;
&lt;p&gt;Family life is a messy distributed system that doesn’t accept incident tickets.&lt;/p&gt;
&lt;h2 id=&#34;what-it-does&#34;&gt;What it does
&lt;/h2&gt;&lt;p&gt;It’s a mobile-first weekly view you can scan quickly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what’s on this week&lt;/li&gt;
&lt;li&gt;what’s coming up soon&lt;/li&gt;
&lt;li&gt;quick ways to add things without filling in a form&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My goal wassn’t “perfect scheduling”, but just to reduce the mental load: if someone asks “what’s on this week?”, the answer becomes “check the planner”.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;this-week-top.png&#34; alt=&#34;Family Planner - This Week screen&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;The whole point: a week you can understand in ten seconds.&lt;/em&gt;
&lt;/p&gt;
&lt;h2 id=&#34;iteration-with-users-was-what-got-it-useful&#34;&gt;Iteration with users was what got it useful
&lt;/h2&gt;&lt;p&gt;I built the first version for me, and it worked, but then I handed it to my family and watched them use it.&lt;/p&gt;
&lt;p&gt;They didn’t care about my neat architecture or costs,they wanted it fast, reliable and easy to use, not another chore to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if adding something takes more than a few seconds, it won’t happen&lt;/li&gt;
&lt;li&gt;if it feels like you can “break it”, people won’t explore&lt;/li&gt;
&lt;li&gt;the best features are usually the ones that remove friction, not the ones that add power&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That feedback loop shaped the app better than I came up with on first iterations.&lt;/p&gt;
&lt;h2 id=&#34;how-i-used-llm-agents-without-making-a-mess&#34;&gt;How I used LLM agents (without making a mess)
&lt;/h2&gt;&lt;p&gt;I did use LLM agents heavily, but not as “magic autopilot”.&lt;/p&gt;
&lt;p&gt;My workflow was more like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;write the rules first (data shape, safety constraints, “what we will not do”)&lt;/li&gt;
&lt;li&gt;treat the agent like a very fast junior developer (literal, eager, needs guardrails)&lt;/li&gt;
&lt;li&gt;ship small changes and actually review them&lt;/li&gt;
&lt;li&gt;test with real people, then iterate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The “rules first” piece matters. It’s the difference between “fast” and “fast, but you still own it afterwards”.&lt;/p&gt;
&lt;p&gt;If you’re curious about this approach, &lt;code&gt;AGENTS.md&lt;/code&gt; is the general idea: &lt;a class=&#34;link&#34; href=&#34;https://agents.md/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://agents.md/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;0month-purposefully&#34;&gt;£0/month purposefully
&lt;/h2&gt;&lt;p&gt;I wanted something we owned, without a subscription, and without handing our family schedule to a random SaaS vendor.&lt;/p&gt;
&lt;p&gt;This runs on Cloudflare’s free tier (for a small household, the numbers are tiny):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Pages: &lt;a class=&#34;link&#34; href=&#34;https://pages.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://pages.cloudflare.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare Workers: &lt;a class=&#34;link&#34; href=&#34;https://workers.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://workers.cloudflare.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;want-the-technical-version&#34;&gt;Want the technical version?
&lt;/h2&gt;&lt;p&gt;I wrote the full technical series separately so this post could stay readable. If you do want the deeper dive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1 (WeekDoc rule + foundations): &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/family-planner-vibe-coding-rules-and-weekdoc/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://michaeldugmore.com/p/family-planner-vibe-coding-rules-and-weekdoc/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 2 (mobile UX + practice): &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/family-planner-vibe-coding-ux-and-practice/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://michaeldugmore.com/p/family-planner-vibe-coding-ux-and-practice/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3 (integrations + notifications): &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/family-planner-vibe-coding-integrations-and-notifications/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://michaeldugmore.com/p/family-planner-vibe-coding-integrations-and-notifications/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 4 (universal add + safety nets): &lt;a class=&#34;link&#34; href=&#34;https://michaeldugmore.com/p/family-planner-vibe-coding-universal-add-and-safety-nets/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://michaeldugmore.com/p/family-planner-vibe-coding-universal-add-and-safety-nets/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;references&#34;&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;OpenAI Codex: &lt;a class=&#34;link&#34; href=&#34;https://openai.com/codex/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://openai.com/codex/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Google Gemini Flash Lite (used purely as a parser): &lt;a class=&#34;link&#34; href=&#34;https://deepmind.google/models/gemini/flash-lite/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://deepmind.google/models/gemini/flash-lite/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Part 4: Fast Capture: Universal Add and the &#34;Trust Layer&#34;</title>
        <link>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-universal-add-and-safety-nets/</link>
        <pubDate>Wed, 31 Dec 2025 11:10:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-universal-add-and-safety-nets/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-universal-add-and-safety-nets/part4-top-image1.png" alt="Featured image of post Part 4: Fast Capture: Universal Add and the &#34;Trust Layer&#34;" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;LLM Agents wrote the code and the bugs. The ideas and most of the wording in this blog series are mine, but edited by LLMs because I am an engineer, not a copywriter. I’ve reviewed it to ensure it sounds like me, but if you spot weird phrasing, blame the robot.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prefer the short version? Start here: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/&#34; &gt;I Built a Family Planner We Actually Use (and It Costs £0/month)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The planner is at its best when you can capture something in three seconds.&lt;/p&gt;
&lt;p&gt;You’re in the kitchen, you remember &amp;ldquo;Parents&amp;rsquo; Evening next Thursday&amp;rdquo;, and you need to get it out of your head. You do not want to open a calendar, select a date, scroll to a time, and fill out a form.&lt;/p&gt;
&lt;p&gt;I wanted a &amp;ldquo;Universal Add&amp;rdquo; button. One text box. Type whatever and let the machine figure it out.&lt;/p&gt;
&lt;p&gt;This introduces risk. If you let an LLM write directly to your database based on a vague user prompt, you invite data corruption.&lt;/p&gt;
&lt;p&gt;This is how I built a smart input with a paranoid &amp;ldquo;Trust Layer&amp;rdquo;.&lt;/p&gt;
&lt;h4 id=&#34;the-architecture-of-universal-add&#34;&gt;The Architecture of Universal Add
&lt;/h4&gt;&lt;p&gt;The flow is strict:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;User Input:&lt;/strong&gt; &amp;ldquo;Gym at 6pm on Tuesday&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parser (Gemini Flash):&lt;/strong&gt; Converts text to a JSON payload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Review:&lt;/strong&gt; The UI shows you what it &lt;em&gt;thinks&lt;/em&gt; you meant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confirmation:&lt;/strong&gt; You click &amp;ldquo;Save&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Nothing gets saved without a human confirmation.&lt;/strong&gt;&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;universal-add-input.png&#34; alt=&#34;Universal Add input&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Capture first. Sort out details next.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;I use &lt;a class=&#34;link&#34; href=&#34;https://deepmind.google/models/gemini/flash-lite/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Google Gemini Flash Lite&lt;/a&gt; (via &lt;a class=&#34;link&#34; href=&#34;https://pages.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cloudflare Pages&lt;/a&gt; Functions) as a parser. I don&amp;rsquo;t use it as a chatbot or an agent. It has a single job: take string, return JSON.&lt;/p&gt;
&lt;h4 id=&#34;ai-output-is-untrusted-input&#34;&gt;AI Output is Untrusted Input
&lt;/h4&gt;&lt;p&gt;In &lt;code&gt;functions/api/universal-add/parse.ts&lt;/code&gt;, I treat the response from Gemini like I would treat a user input from the public internet: with extreme suspicion.&lt;/p&gt;
&lt;p&gt;It passes through strict validation logic. Does the date exist? Is the ISO format correct? If the LLM hallucinates a 13th month or a time of &amp;ldquo;25:00&amp;rdquo;, the API throws an error. The browser never sees the API keys; the server handles the handshake.&lt;/p&gt;
&lt;p&gt;This costs nothing because the volume for a family app is within free tier limits, but the safeguards make it reliable.&lt;/p&gt;
&lt;h4 id=&#34;backups&#34;&gt;Backups
&lt;/h4&gt;&lt;p&gt;When a side project contains your actual life schedule, &amp;ldquo;it&amp;rsquo;s just a KV store&amp;rdquo; stops being funny.&lt;/p&gt;
&lt;p&gt;I built a blunt, heavy-handed backup system.&lt;/p&gt;
&lt;p&gt;The Export dumps the entire KV namespace to a JSON file. The Import restores from JSON but &lt;strong&gt;defaults to safe-mode&lt;/strong&gt; (no overwrites).&lt;/p&gt;
&lt;p&gt;If you want to overwrite existing data you have to toggle a switch and confirm it. A backup you haven&amp;rsquo;t restored is just a wish, so I tested the restore flow before I let the family start adding real data.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;backup-screen.png&#34; alt=&#34;Backup / Restore screen&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Download a snapshot, import it back with explicit overwrite protection.&lt;/em&gt;
&lt;/p&gt;
&lt;h4 id=&#34;the-admin-gate&#34;&gt;The &amp;ldquo;Admin&amp;rdquo; Gate
&lt;/h4&gt;&lt;p&gt;To prevent unauthorized access or accidental restores, the backup API checks the Cloudflare Access email header against an allowlist in &lt;code&gt;functions/_lib/backupAuth.ts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It’s a simple check that prevents accidental history rewrites.&lt;/p&gt;
&lt;h4 id=&#34;ownership&#34;&gt;Ownership
&lt;/h4&gt;&lt;p&gt;This project cost me £0 in hosting. It uses &amp;ldquo;AI&amp;rdquo;, but not in a way that takes control away from me.&lt;/p&gt;
&lt;p&gt;I used the Agents to write the boilerplate, the parsers, and the React components. But I kept the &amp;ldquo;Senior Engineer&amp;rdquo; hat on. I defined the data model (&lt;code&gt;AGENTS.md&lt;/code&gt;), I set the safety rules, and I determined the architecture.&lt;/p&gt;
&lt;p&gt;The result is a tool that feels incredibly personal because it fits our life exactly. And because I own the code, it will grow as we do.&lt;/p&gt;
&lt;p&gt;If you’re thinking of &amp;ldquo;vibe coding&amp;rdquo; an app: &lt;strong&gt;Write your rules first.&lt;/strong&gt; The AI is the engine, but you need to be the steering wheel.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Google Gemini Flash Lite: &lt;a class=&#34;link&#34; href=&#34;https://deepmind.google/models/gemini/flash-lite/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://deepmind.google/models/gemini/flash-lite/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare Pages: &lt;a class=&#34;link&#34; href=&#34;https://pages.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://pages.cloudflare.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenAI Codex: &lt;a class=&#34;link&#34; href=&#34;https://openai.com/codex/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://openai.com/codex/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AGENTS.md: &lt;a class=&#34;link&#34; href=&#34;https://agents.md/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://agents.md/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Part 3: The Stuff You Notice When It Breaks: Term Dates and Bin Days</title>
        <link>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-integrations-and-notifications/</link>
        <pubDate>Tue, 30 Dec 2025 10:40:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-integrations-and-notifications/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-integrations-and-notifications/part3-top-image1.png" alt="Featured image of post Part 3: The Stuff You Notice When It Breaks: Term Dates and Bin Days" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;LLM Agents wrote the code and the bugs. The ideas and most of the wording in this blog series are mine, but edited by LLMs because I am an engineer, not a copywriter. I’ve reviewed it to ensure it sounds like me, but if you spot weird phrasing, blame the robot.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prefer the short version? Start here: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/&#34; &gt;I Built a Family Planner We Actually Use (and It Costs £0/month)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Family data falls into two categories.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Internal:&lt;/strong&gt; &amp;ldquo;Dinner at Grandma&amp;rsquo;s&amp;rdquo;. We control this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External:&lt;/strong&gt; &amp;ldquo;Inset Day&amp;rdquo; or &amp;ldquo;Recycling Collection&amp;rdquo;. We do not control this, it changes without warning, and forgetting it causes chaos.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I wanted the planner to handle the second kind. This meant writing integrations.&lt;/p&gt;
&lt;p&gt;In a professional context I’d look for an enterprise API with an SLA. In the context of my local council and school I’m looking at messy HTML and obscure JSON endpoints.&lt;/p&gt;
&lt;p&gt;This is how I used &lt;a class=&#34;link&#34; href=&#34;https://workers.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cloudflare Workers&lt;/a&gt; to ingest annoying schedules without polluting my clean database.&lt;/p&gt;
&lt;h4 id=&#34;the-overlay-strategy&#34;&gt;The &amp;ldquo;Overlay&amp;rdquo; Strategy
&lt;/h4&gt;&lt;p&gt;I stood firm on my data rule: &lt;strong&gt;External data never touches the WeekDoc.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If I merged school dates into our primary weekly JSON, a scraper bug could corrupt our personal schedule. Instead, Term Dates and Bin Collections are stored as separate KV documents. The UI renders them as visual overlays.&lt;/p&gt;
&lt;p&gt;If the school website changes its layout and my scraper fails, the overlay disappears, but our family calendar remains intact. This is standard defensive coding.&lt;/p&gt;
&lt;h4 id=&#34;scraping-school-dates&#34;&gt;Scraping School Dates
&lt;/h4&gt;&lt;p&gt;Schools generally provide PDF links or HTML tables rather than APIs.&lt;/p&gt;
&lt;p&gt;I wrote a refresher script (&lt;code&gt;src/shared/schoolDatesRefresh.ts&lt;/code&gt;) that uses &lt;code&gt;HTMLRewriter&lt;/code&gt; to scrape the term dates. This code is brittle and boring to write.&lt;/p&gt;
&lt;p&gt;I let the Agent write it. I gave it the HTML structure and instructed it to extract the dates, normalize them to ISO strings, and hash the result.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;school-dates.png&#34; alt=&#34;School dates screen&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Term dates as data, not a link you have to remember.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;If the hash hasn&amp;rsquo;t changed we don&amp;rsquo;t write to KV. This keeps the &amp;ldquo;Last Updated&amp;rdquo; timestamp meaningful. We assume the scrape is hostile; if the data doesn&amp;rsquo;t validate strictly, we discard it.&lt;/p&gt;
&lt;h4 id=&#34;bin-collections&#34;&gt;Bin Collections
&lt;/h4&gt;&lt;p&gt;I prepared to scrape the council website but found a hidden backend API. It requires a UPRN (property identifier) which I injected via environment variables.&lt;/p&gt;
&lt;p&gt;This runs on a scheduled Worker. It fetches the data, normalizes it, and stores it in its own KV key. The UI checks for a bin overlay for the current week and renders it.&lt;/p&gt;
&lt;h4 id=&#34;push-notifications&#34;&gt;Push Notifications
&lt;/h4&gt;&lt;p&gt;I originally avoided Push because it feels intrusive. After missing an event because I didn&amp;rsquo;t look at the phone, I changed my mind.&lt;/p&gt;
&lt;p&gt;The architecture is simple. A Service Worker handles the incoming push. A Dispatcher Worker runs on a cron trigger every minute. We track sent message IDs in KV to ensure we don&amp;rsquo;t spam the family if the worker retries.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;notifications-modal.png&#34; alt=&#34;Notifications modal&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Enable/disable/test push per device. No hidden magic.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;This is the only part of the app that feels like traditional infrastructure.&lt;/p&gt;
&lt;p&gt;This is unglamorous plumbing, but it is necessary. It is also where LLM Agents excel. I can paste a curled HTML response into the chat and ask for a robust parser using Zod. The Agent handles the regex, and I handle the architecture that ensures a bad regex doesn&amp;rsquo;t bring down the app.&lt;/p&gt;
&lt;p&gt;Next: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-universal-add-and-safety-nets/&#34; &gt;Part 4: Fast Capture: Universal Add and the &amp;ldquo;Trust Layer&amp;rdquo;&lt;/a&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Part 2: Making It Stick: Mobile UX and Atomic Habits</title>
        <link>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-ux-and-practice/</link>
        <pubDate>Tue, 30 Dec 2025 10:20:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-ux-and-practice/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-ux-and-practice/part2-top-image1.png" alt="Featured image of post Part 2: Making It Stick: Mobile UX and Atomic Habits" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;LLM Agents wrote the code and the bugs. The ideas and most of the wording in this blog series are mine, but edited by LLMs because I am an engineer, not a copywriter. I’ve reviewed it to ensure it sounds like me, but if you spot weird phrasing, blame the robot.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prefer the short version? Start here: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/&#34; &gt;I Built a Family Planner We Actually Use (and It Costs £0/month)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ve built software for years and I still forget that I am not the user.&lt;/p&gt;
&lt;p&gt;I built the first version of the planner for myself. It was functional and dense. Then I handed it to my partner and son.&lt;/p&gt;
&lt;p&gt;They didn&amp;rsquo;t care about the architecture. They hesitated. They tapped things that weren&amp;rsquo;t buttons. I watched them struggle with the forms and realized that if adding an event takes more than five seconds, nobody will do it.&lt;/p&gt;
&lt;p&gt;That session shifted the focus from displaying data to reducing friction.&lt;/p&gt;
&lt;h4 id=&#34;designing-for-one-thumb&#34;&gt;Designing for One Thumb
&lt;/h4&gt;&lt;p&gt;Most usage happens in the kitchen, usually while holding a bag of shopping.&lt;/p&gt;
&lt;p&gt;The original &amp;ldquo;Edit&amp;rdquo; modal was a standard form. This was too slow. We moved to &lt;strong&gt;Inline Add&lt;/strong&gt;. You tap a day, type &amp;ldquo;Pizza&amp;rdquo;, and hit enter. Complex forms stop people from using the tool.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;events-inline-add.png&#34; alt=&#34;Inline event add UI&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Inline add keeps event creation quick.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;I also built a Read-Only view so I could check the schedule without fear of dragging an event into the trash while scrolling. This makes the app feel safe when you aren&amp;rsquo;t trying to change anything.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;share-view-top.png&#34; alt=&#34;Share view enabled&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Read-only mode is often the difference between “interesting” and “used”.&lt;/em&gt;
&lt;/p&gt;
&lt;h4 id=&#34;the-practice-tab-atomic-habits&#34;&gt;The Practice Tab (Atomic Habits)
&lt;/h4&gt;&lt;p&gt;Once the planner was sticky I wanted to tackle a secondary goal: habits.&lt;/p&gt;
&lt;p&gt;I finally read &lt;a class=&#34;link&#34; href=&#34;https://jamesclear.com/atomic-habits&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;em&gt;Atomic Habits&lt;/em&gt;&lt;/a&gt; by James Clear this year. The core concept resonated with me: you don&amp;rsquo;t rise to the level of your goals, you fall to the level of your systems. Small, repeated actions matter more than heroic bursts of effort.&lt;/p&gt;
&lt;p&gt;I wanted to incorporate that into family life without the annoyance. I dislike the &amp;ldquo;streak&amp;rdquo; gamification found in most apps because it inevitably leads to guilt.&lt;/p&gt;
&lt;p&gt;I built a &amp;ldquo;Practice&amp;rdquo; tab with specific constraints in a nested &lt;code&gt;AGENTS.md&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No streaks&lt;/li&gt;
&lt;li&gt;No failure states&lt;/li&gt;
&lt;li&gt;Just dots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It tracks sessions. If you practice, you get a dot. If you don&amp;rsquo;t, nothing happens.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;practice-family.png&#34; alt=&#34;Practice family dashboard&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;A family-wide view: days practiced per person, per skill, this week.&lt;/em&gt;
&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;practice-person.png&#34; alt=&#34;Practice person dashboard&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;One-tap logging, optional details.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;Technically this uses a separate KV document (&lt;code&gt;practice:v1&lt;/code&gt;) to avoid polluting the &lt;code&gt;WeekDoc&lt;/code&gt;. This architectural choice keeps the habit data independent from the calendar data.&lt;/p&gt;
&lt;h4 id=&#34;the-phone-pr-workflow&#34;&gt;The &amp;ldquo;Phone PR&amp;rdquo; Workflow
&lt;/h4&gt;&lt;p&gt;This phase of development was less about architecture and more about rapid iteration.&lt;/p&gt;
&lt;p&gt;I used the &amp;ldquo;phone workflow&amp;rdquo; heavily. I’d spot a friction point during breakfast, open a PR via &lt;a class=&#34;link&#34; href=&#34;https://openai.com/codex/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Codex&lt;/a&gt; Web on my phone, review the diff, and merge it. Cloudflare deployed it before I finished my coffee.&lt;/p&gt;
&lt;p&gt;This is where the &amp;ldquo;vibe coding&amp;rdquo; approach works best. Fixing a small CSS padding issue or a confusing button label shouldn&amp;rsquo;t require opening a laptop.&lt;/p&gt;
&lt;p&gt;The app only started working when I stopped designing for an engineer and started designing for a family. Inline Add removes the admin friction, and the Practice tab provides a gentle nudge without the guilt trip.&lt;/p&gt;
&lt;p&gt;Next: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-integrations-and-notifications/&#34; &gt;Part 3: The Stuff You Notice When It Breaks: Term Dates and Bin Days&lt;/a&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Part 1: I Vibe-Coded a Family Planner (Because Family Life Doesn&#39;t Accept Incident Tickets) all for £0</title>
        <link>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-rules-and-weekdoc/</link>
        <pubDate>Mon, 29 Dec 2025 10:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-rules-and-weekdoc/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-rules-and-weekdoc/part1-top-image1.png" alt="Featured image of post Part 1: I Vibe-Coded a Family Planner (Because Family Life Doesn&#39;t Accept Incident Tickets) all for £0" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;LLM Agents wrote the code and the bugs. The ideas and most of the wording in this blog series are mine, but edited by LLMs because I am an engineer, not a copywriter. I’ve reviewed it to ensure it sounds like me, but if you spot weird phrasing, blame the robot.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prefer the short version? Start here: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-the-whole-story/&#34; &gt;I Built a Family Planner We Actually Use (and It Costs £0/month)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My brain can hold an entire backlog grooming session, the moving parts of a Cloudflare deployment, and six &amp;ldquo;good ideas&amp;rdquo; I’ll never ship. But it cannot reliably remember whether it’s PE kit day, whose turn it is to take the bins out, or what we said we were doing next Saturday.&lt;/p&gt;
&lt;p&gt;Family life is a messy distributed system that I hope to try and tame a little anyway I can.&lt;/p&gt;
&lt;p&gt;Over the Christmas break I built a mobile-first weekly planner for my partner, my son, and me. The goal was to put the stuff we keep forgetting in one place, in a shape that matches how we actually talk about a week.&lt;/p&gt;
&lt;p&gt;I set two constraints. It had to cost £0 (running on Cloudflare’s free tier) and I wanted to &amp;ldquo;vibe code&amp;rdquo; it using LLM agents. As an Engineering Lead, I know that &amp;ldquo;fast&amp;rdquo; usually means &amp;ldquo;unmaintainable mess&amp;rdquo; unless you set the rules first.&lt;/p&gt;
&lt;p&gt;This is the stack, the &amp;ldquo;WeekDoc&amp;rdquo; rule, and how I forced an LLM to write code I actually wanted to own.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;this-week-top.png&#34; alt=&#34;Family Planner - This Week screen&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;A weekly dashboard you can understand in ten seconds.&lt;/em&gt;
&lt;/p&gt;
&lt;h4 id=&#34;the-boring-stack&#34;&gt;The &amp;ldquo;Boring&amp;rdquo; Stack
&lt;/h4&gt;&lt;p&gt;There are plenty of existing apps for this. Most come with a subscription, a data model I can&amp;rsquo;t control, and a roadmap I can&amp;rsquo;t influence.&lt;/p&gt;
&lt;p&gt;I wanted full ownership for zero pence. The architecture is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React + Vite + TypeScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API:&lt;/strong&gt; &lt;a class=&#34;link&#34; href=&#34;https://pages.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cloudflare Pages&lt;/a&gt; Functions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; &lt;a class=&#34;link&#34; href=&#34;https://developers.cloudflare.com/kv/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cloudflare KV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scheduled tasks:&lt;/strong&gt; &lt;a class=&#34;link&#34; href=&#34;https://workers.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cloudflare Workers&lt;/a&gt; (cron triggers)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It lives behind Cloudflare Zero Trust. Locally it works without it. In prod the backend picks up the user email from the access headers.&lt;/p&gt;
&lt;h4 id=&#34;the-weekdoc-rule-one-document---easy-to-understand-and-less-complexity&#34;&gt;The WeekDoc Rule: One Document - easy to understand and less complexity
&lt;/h4&gt;&lt;p&gt;The biggest risk with AI-assisted coding is complexity creep. Agents love to glue new database tables onto the side, creating synchronization issues.&lt;/p&gt;
&lt;p&gt;To prevent this I wrote the project &amp;ldquo;Constitution&amp;rdquo; before writing any code. It lives in &lt;code&gt;AGENTS.md&lt;/code&gt; at the root of the repo.&lt;/p&gt;
&lt;p&gt;The core law is the &lt;strong&gt;WeekDoc Rule&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The app edits one full JSON document per week. It saves via a full replacement &lt;code&gt;PUT&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One week equals one key in KV (&lt;code&gt;week:2025-W01&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t the most scalable database design, but it is the right one for a family of three. The enemy here is complexity, not throughput. Treating the week as a single document eliminates partial update bugs entirely. We read the week, edit it in memory, and save the whole thing.&lt;/p&gt;
&lt;h4 id=&#34;managing-the-ai&#34;&gt;Managing the AI
&lt;/h4&gt;&lt;p&gt;&amp;ldquo;Vibe coding&amp;rdquo; often ends up being a mess of pasted code.&lt;/p&gt;
&lt;p&gt;I treated the AI Agents (&lt;a class=&#34;link&#34; href=&#34;https://openai.com/codex/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Codex&lt;/a&gt;/Cursor) like a really fast, Junior Engineer. My job was to write the specs and the guardrails, not the implementation details.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rules first.&lt;/strong&gt; &lt;a class=&#34;link&#34; href=&#34;https://agents.md/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt; defines the data structure and the optimistic concurrency (version numbers) required to prevent overwrite conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code review.&lt;/strong&gt; The AI implemented the types and the API, but I reviewed every PR.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architecture enforcement.&lt;/strong&gt; If the AI tried to introduce a new state library, I pointed it back to the design docs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation of concerns allowed us to move fast while keeping the codebase structure clean.&lt;/p&gt;
&lt;h4 id=&#34;the-result&#34;&gt;The Result
&lt;/h4&gt;&lt;p&gt;The repo is public here: &lt;a class=&#34;link&#34; href=&#34;https://github.com/mdugmore/family-planner-public&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;github.com/mdugmore/family-planner-public&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One note: that public repo has no meaningful commit history. My original private repo’s history contained some personal &amp;gt; data, so I made a clean public version instead. My plan is to converge the private and public repos over time so it becomes a truly open-source project and new enhancements can land publicly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When someone asks &amp;ldquo;what’s on this week?&amp;rdquo;, the answer is &amp;ldquo;check the planner&amp;rdquo;. The mental load is gone and the hosting bill is £0.&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;archive-modal.png&#34; alt=&#34;Archive modal&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Archive is explicit, with a small carry-over chooser.&lt;/em&gt;
&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img src=&#34;calendar-overlays.png&#34; alt=&#34;Calendar showing overlays&#34; width=&#34;360px&#34; /&gt;&lt;br/&gt;
  &lt;em&gt;Events plus optional overlays (school + bins).&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;Next: &lt;a class=&#34;link&#34; href=&#34;https://michael-dugmore.pages.dev/p/family-planner-vibe-coding-ux-and-practice/&#34; &gt;Part 2: Making It Stick: Mobile UX and Atomic Habits&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Cloudflare Workers: &lt;a class=&#34;link&#34; href=&#34;https://workers.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://workers.cloudflare.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare Pages: &lt;a class=&#34;link&#34; href=&#34;https://pages.cloudflare.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://pages.cloudflare.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenAI Codex: &lt;a class=&#34;link&#34; href=&#34;https://openai.com/codex/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://openai.com/codex/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AGENTS.md: &lt;a class=&#34;link&#34; href=&#34;https://agents.md/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://agents.md/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Vendor Lock-in, AI Freedom, and an Engineering Lead&#39;s Mixed Emotions</title>
        <link>https://michael-dugmore.pages.dev/p/vendor-lockin-ai-freedom/</link>
        <pubDate>Mon, 05 May 2025 17:10:07 +0100</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/vendor-lockin-ai-freedom/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/vendor-lockin-ai-freedom/Ai-mirror.jpg" alt="Featured image of post Vendor Lock-in, AI Freedom, and an Engineering Lead&#39;s Mixed Emotions" /&gt;&lt;blockquote&gt;
&lt;p&gt;Note: This post was a braindump of ideas, then refined a little using LLM&amp;rsquo;s, but have tried to minimise its the use as we all see way too much AI slop on the web now. I just to made it a little more coherent than my braindump.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;from-autocomplete-to-autonomous-agents-my-ai-journey&#34;&gt;From Autocomplete to Autonomous Agents: My AI Journey
&lt;/h3&gt;&lt;p&gt;I&amp;rsquo;ve been experimenting with AI since the GPT-2 days, playing with early versions that felt like slightly smarter autocomplete tools, and lately diving deep into full-blown coding agents that build entire solutions from simple specs. It&amp;rsquo;s been both fascinating and unsettling.&lt;/p&gt;
&lt;h3 id=&#34;redefining-the-developers-role&#34;&gt;Redefining the Developer&amp;rsquo;s Role
&lt;/h3&gt;&lt;p align=&#34;center&#34;&gt;
    &lt;img src=&#34;dread-ai.jpg&#34; alt=&#34;Dread the AI&#34; width=&#34;400px&#34; /&gt;&lt;br/&gt;
&lt;/p&gt;
Watching an agent read a short description and then quickly churn out functional code, often within minutes, leaves me simultaneously amazed and slightly horrified. Immediately, my mind asks, &#34;What&#39;s been the point of my last 20 years of coding?&#34; It feels somewhat existential. But then again, maybe I comfort myself with thoughts of the past: when IDE autocomplete came about, plenty of developers saw it as cheating, worried it would dull our coding skills. Or think about the rise of low-code and no-code solutions. Great for simple tasks, sure, but that tricky 20% of customisation still takes up 90% of the headaches. Maybe these AI coding agents are on a similar trajectory.
&lt;p&gt;Today, these agents still get stuck and need human engineers with experience and good system design intuition to nudge them along, much like mentoring junior developers, albeit ones who learn incredibly fast. But it&amp;rsquo;s clear they&amp;rsquo;re rapidly improving, and the line between junior dev (and to be honest mid to senior devs in certain domains) and agent is getting increasingly blurred.&lt;/p&gt;
&lt;h3 id=&#34;rethinking-vendors--outsourcing-ai-as-the-new-development-partner&#34;&gt;Rethinking Vendors &amp;amp; Outsourcing: AI as the New Development Partner
&lt;/h3&gt;&lt;p&gt;What really intrigues me is what this means for the wider tech industry, especially in sectors reliant on big, expensive SaaS providers. Imagine, for instance, a bank (which I know a thing or two about) using costly vendors for compliance or KYC solutions. Typically, these systems are generic and built for broad markets, forcing banks to adapt their processes to the software. Integration becomes a hassle, costly and time-consuming. Now imagine cutting out that vendor, building something tailored directly to the bank&amp;rsquo;s needs using an AI agent. Integration suddenly becomes the easier part, and the business gets control over its solution roadmap. Sure, you&amp;rsquo;ll still need domain expertise, but guess what? AI&amp;rsquo;s ability to deep-dive into research could bridge that gap too. Of course, while AI can research regulations, the critical judgment and ultimate accountability for applying that knowledge correctly, especially in tightly regulated areas like finance, still has to rest firmly with experienced humans.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also thinking about the outsourcing industry. In my experience, if your specs aren&amp;rsquo;t rock-solid, outsourcing can quickly turn into a frustrating, drawn-out ordeal. Interestingly, the same applies to working with AI agents: vague requirements mean poor outcomes. But here&amp;rsquo;s the thing: if you&amp;rsquo;re going to spend all that effort writing impeccable specs, why not feed them to an AI agent instead of negotiating contracts with an outsourcing firm? Could this mean the end of traditional software outsourcing? Mind you, ensuring the AI truly understood the nuances, or figuring out how to handle maintenance when the underlying AI model itself evolves, definitely presents a whole new set of hurdles to replace the old ones. (But I will hand wave that away for now as I don&amp;rsquo;t have the answers)&lt;/p&gt;
&lt;p&gt;But before panic sets in, let&amp;rsquo;s step back a moment. Is this really a doomsday scenario for software developers, or could it actually be more like the internet boom of the late 90s, full of opportunities for those who adapt? Certainly, if you&amp;rsquo;re not keeping pace with these new developments, things look bleak. But, let&amp;rsquo;s face it, keeping up is exhausting. Perhaps adaptation for experienced devs means shifting focus even more towards the things AI doesn&amp;rsquo;t easily replicate: robust architecture, insightful system design, rigorous validation, mastering the art of guiding the AI (prompt engineering, if you like), and providing that crucial human-in-the-loop oversight. Maybe the best we can do right now is leverage AI tools to stay ahead, at least temporarily.&lt;/p&gt;
&lt;h3 id=&#34;empowering-non-engineers-the-rise-of-citizen-developers&#34;&gt;Empowering Non-Engineers: The Rise of Citizen Developers
&lt;/h3&gt;&lt;p&gt;One thing that genuinely excites me is seeing non-engineers build things with tools like Replit or Lovable. Watching their ideas quickly come to life gives them the same spark of joy that initially drew me into programming, without the barrier of years of coding experience. This might feel threatening initially, &amp;ldquo;Are they taking our jobs?&amp;rdquo;, but actually, these fresh minds often bring completely new perspectives and ideas that can birth entirely new businesses overnight. And new businesses mean new opportunities, especially for integration, support, and infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;touch-grass&#34;&gt;&amp;ldquo;Touch Grass&amp;rdquo;
&lt;/h3&gt;&lt;p align=&#34;center&#34;&gt;
    &lt;img src=&#34;mayfayre.jpg&#34; alt=&#34;Local Village Mayfayre&#34; width=&#34;400px&#34; /&gt;&lt;br/&gt;
    &lt;em&gt;Local Village Mayfayre&lt;/em&gt;
&lt;/p&gt;
This weekend, while walking around a local village market, I found myself surrounded by stalls selling food, crafts, beer, with kids running around playing cricket and a band playing. People were interacting naturally, and the pace felt refreshingly slower. Not a single conversation touched on AI (and don’t worry, I didn’t bring it up either). Being at this village market made me realise how much my head has been spinning recently with AI, coding, and researching stuff online. It was nice just slowing down a bit, being around real things and chatting to actual people about everyday stuff. It reminded me how important it is to step away, slow down, and, as my son likes to say, &#34;touch grass&#34; now and then. Staying connected to your local community and chatting with people face-to-face matters. If AI really does end up taking over a lot of stuff, at least we&#39;re all in the same boat, and looking after each other becomes even more important.
&lt;h3 id=&#34;challenges-and-opportunities-ahead&#34;&gt;Challenges and Opportunities Ahead
&lt;/h3&gt;&lt;p&gt;Ultimately, I&amp;rsquo;m conflicted, but mostly excited. Having ridden the rollercoaster of AI hype for several years now, I sense we&amp;rsquo;re hitting a maturity plateau, where these tools become genuinely useful, integrated into our workflows rather than replacing them completely. Of course, another explosive breakthrough could be just around the corner. But even if there isn&amp;rsquo;t, one thing is clear: AI isn&amp;rsquo;t going away. Our challenge, and opportunity, lies in figuring out how best to adapt.&lt;/p&gt;
&lt;p&gt;I can talk for hours about this topic, but tried to keep it relatively focused and I would like to expand on how this is affecting non developers too. I will try keep putting my thoughts out there as I get some time and I am not either vibe coding, or &amp;ldquo;Touching Grass&amp;rdquo;&lt;/p&gt;
&lt;hr&gt;
</description>
        </item>
        <item>
        <title>The Long Ride: Designing Robust Systems Like a Bike Packer on King Alfred&#39;s Way</title>
        <link>https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/</link>
        <pubDate>Sat, 26 Apr 2025 10:00:00 +0100</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_081013417.MP.jpg" alt="Featured image of post The Long Ride: Designing Robust Systems Like a Bike Packer on King Alfred&#39;s Way" /&gt;&lt;p&gt;There&amp;rsquo;s a unique satisfaction that comes from loading up your bike with everything you need and setting off into the unknown. A few years ago, some friends and I got hooked on the idea of gravel cycling – exploring bridleways, forest tracks, and quiet lanes away from the traffic. We perhaps dreamt of dusty, sun-baked American-style gravel roads, but quickly discovered the UK reality often involves&amp;hellip; well, a lot more mud. Glorious, character-building mud!&lt;/p&gt;
&lt;p&gt;This experience, especially tackling longer multi-day routes, highlighted stark parallels with my day job as a Lead Engineer in banking: designing and building robust software systems, often leveraging cloud platforms like &lt;strong&gt;Azure&lt;/strong&gt;. The challenges might seem worlds apart – navigating a muddy bridleway versus deploying critical payment code – but the underlying principles of preparation, resilience, and adaptability are remarkably similar. Thinking like a bike packer, especially one planning for the unpredictable British weather, can make us better system designers.&lt;/p&gt;
&lt;p&gt;One route that crystallised this for me was the &lt;a class=&#34;link&#34; href=&#34;https://www.cyclinguk.org/routes/long-distance/king-alfreds-way&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;King Alfred&amp;rsquo;s Way&lt;/a&gt;&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
    &lt;img src=&#34;images/route-map.jpg&#34; alt=&#34;King Alfred&#39;s Way Route Map&#34; width=&#34;400px&#34; /&gt;&lt;br/&gt;
    &lt;em&gt;King Alfred&#39;s Way Route Map&lt;/em&gt;
&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
    &lt;img src=&#34;images/PXL_20210911_084049222.jpg&#34; alt=&#34;Stone Henge&#34;  /&gt;&lt;br/&gt;
    &lt;em&gt;My Loaded bike at a stop at Stone Henge!!&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;Here’s how the planning and execution of a ride like that mirrors system design:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Planning the Route (King Alfred&amp;rsquo;s Way): Defining Requirements &amp;amp; Scope&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing (KAW):&lt;/strong&gt; King Alfred&amp;rsquo;s Way is a stunning, roughly 350km (220-mile) circular route looping through the heart of historic Wessex. It connects Winchester, Salisbury, Stonehenge, Avebury, and Reading across varied terrain. Planning involves studying the map, estimating daily mileage, identifying resupply points, checking accommodation/camping options, and noting bail-out points. You define the objective, constraints, and key milestones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; This mirrors requirements gathering. What problem are we solving (&amp;ldquo;destination&amp;rdquo;)? Who are the users? What are the critical user journeys (&amp;ldquo;key sections&amp;rdquo;)? What are the non-functional requirements like performance, availability, security (&amp;ldquo;terrain challenges&amp;rdquo;)? What are the constraints (budget, team skills, existing infrastructure - &amp;ldquo;time, fitness, gear&amp;rdquo;)? Clear definition prevents building the wrong thing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Choosing Your Gear: Selecting Technology &amp;amp; Components (Simplicity vs. Complexity)&lt;/strong&gt;&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
    &lt;img src=&#34;images/packing.jpg&#34; alt=&#34;Packing Items&#34;  /&gt;&lt;br/&gt;
    &lt;em&gt;Not actual packing list!&lt;/em&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing (KAW &amp;amp; UK Gravel):&lt;/strong&gt; Gear choice for UK gravel is critical. Tyre choice is key for mud. Reliability matters – trustworthy brakes, solid drivetrain, waterproof bags. But there&amp;rsquo;s a trade-off: sometimes the fanciest gear is the hardest to fix in the field. Simple, robust components might be heavier but their field serviceability can be invaluable compared to complex parts requiring specialist tools. You choose gear appropriate for the &lt;em&gt;actual&lt;/em&gt; conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; This maps to selecting your technology stack (languages, frameworks, databases, &lt;strong&gt;Azure&lt;/strong&gt; components, APIs). Are they mature and reliable (&amp;ldquo;durable tyres&amp;rdquo;)? Do they scale? Are they cost-effective? Crucially, consider the complexity trade-off. Sometimes the latest pattern introduces significant overhead. A simpler, well-understood approach might be more robust and easier to debug under pressure. Choose tools and patterns appropriate for the &lt;em&gt;real-world&lt;/em&gt; operational demands, including maintainability. Avoid unnecessary complexity just because you can.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Packing Smart: System Architecture &amp;amp; Organization (Choosing the Right Structure)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing:&lt;/strong&gt; How you load your bike affects handling. Heavy items low and central. Frequently needed items easily accessible. Protecting sensitive gear. It’s about organisation, balance, and quick access. Different packing systems exist (panniers, frame bags, large saddlebags) – each has pros and cons for weight distribution, capacity, and accessibility depending on the bike and trip length.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; This is your system architecture – how you structure your code and components. &lt;strong&gt;Choosing the right architectural pattern is like choosing your packing system.&lt;/strong&gt; A &lt;strong&gt;monolith&lt;/strong&gt; might be simpler initially, like one large bag – everything&amp;rsquo;s together, but accessing or changing one part can affect the whole load. A more &lt;strong&gt;modular or distributed architecture&lt;/strong&gt; (like using separate services or libraries) is akin to using multiple specialised bags – it allows for independent changes and deployments (&amp;ldquo;accessing one bag doesn&amp;rsquo;t require unpacking everything&amp;rdquo;) but requires more careful planning on how the pieces interact (&amp;ldquo;ensuring the bags fit and balance well&amp;rdquo;). The key is selecting an architecture (like modularity, separation of concerns, clear interfaces) that provides the right balance of maintainability (&amp;ldquo;easy access&amp;rdquo;), stability (&amp;ldquo;good balance&amp;rdquo;), and independent evolution for &lt;em&gt;your specific needs and team&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Redundancy &amp;amp; Repair Kits: Fault Tolerance &amp;amp; Resilience (Decoupling &amp;amp; The Hanger Incident!)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing:&lt;/strong&gt; Things &lt;em&gt;will&lt;/em&gt; go wrong. Punctures, loose bolts, broken chains. You carry a repair kit, know basic repairs, and have backup navigation. &lt;strong&gt;Crucially, you anticipate specific catastrophic failures.&lt;/strong&gt; On our King Alfred&amp;rsquo;s Way trip, disaster struck. Hitting a deep, muddy rut, one friend&amp;rsquo;s derailleur hanger snapped. Without a spare, the ride is over. &lt;strong&gt;This would have been catastrophic&amp;hellip; but it wasn&amp;rsquo;t.&lt;/strong&gt; Anticipating this vulnerability, &lt;em&gt;all of us&lt;/em&gt; carried spares, sourced preemptively (one even from China!). That small, redundant part saved the trip.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; Systems fail. Networks glitch, servers crash, dependencies become unavailable. We design for failure. Cloud platforms like &lt;strong&gt;Azure&lt;/strong&gt; provide built-in redundancy (e.g., Availability Zones are like multiple route options). We also build resilience through &lt;strong&gt;decoupling components&lt;/strong&gt;. If one part of the system needs to communicate with another, using techniques like &lt;strong&gt;asynchronous processing or message queues&lt;/strong&gt; means the sender can continue its work even if the receiver is temporarily down. The request waits patiently (&amp;ldquo;the snack in your bag&amp;rdquo;) until the downstream component recovers. This prevents failures in one area from cascading and taking down the entire system. Just like carrying that specific derailleur hanger, anticipating critical failure points and building in redundancy and decoupling (spare components, failover systems, queued/asynchronous processing) is fundamental, especially in high-stakes banking environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Navigating the Unknown: Monitoring, Observability &amp;amp; Adaptability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing (KAW):&lt;/strong&gt; Routes aren&amp;rsquo;t always perfectly marked, GPS signals drop, trails get diverted. You constantly check your position, monitor energy/supplies, watch the weather, and adapt. A muddy track might force a road detour. Slower progress might change your overnight stop. Situational awareness is key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; This is observability. Logging, metrics (e.g., via &lt;strong&gt;Azure Monitor&lt;/strong&gt;), and tracing provide real-time understanding. Dashboards show health (&amp;ldquo;position/speed&amp;rdquo;), alerts notify of problems (&amp;ldquo;blocked trail&amp;rdquo;), logs/traces help diagnose issues (&amp;ldquo;why are we slow?&amp;rdquo;). Monitoring tells you if you&amp;rsquo;re &amp;ldquo;on course&amp;rdquo; (meeting SLOs) or need to adapt (scale resources, deploy a fix, investigate anomalies).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. Pacing Yourself: Performance, Scalability &amp;amp; Sustainability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike Packing:&lt;/strong&gt; KAW is a long route. You need a sustainable pace, managing energy, fuel, and rest to finish without burnout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Design:&lt;/strong&gt; Systems need to perform under load and scale (&lt;strong&gt;Azure&lt;/strong&gt; offers auto-scaling capabilities). This requires efficient design (algorithms, indexing, caching). It also means building &lt;em&gt;sustainably&lt;/em&gt; – managing technical debt (&amp;ldquo;not carrying unnecessary weight&amp;rdquo;), writing clean code (&amp;ldquo;well-serviced bike&amp;rdquo;), ensuring the system can evolve. Build for the long haul.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Journey is the Reward (Mud, Snapped Parts, and All)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Both bike packing King Alfred&amp;rsquo;s Way and designing complex software systems require meticulous planning blended with the ability to improvise when faced with the unexpected – a flooded trail, a snapped derailleur hanger, or a sudden spike in transaction volume. They demand understanding trade-offs (like simplicity vs. features, or monolithic vs. distributed architectures), a laser focus on reliability, and the foresight to prepare for specific failures through redundancy and decoupling.&lt;/p&gt;
&lt;p&gt;So, the next time you&amp;rsquo;re deep in a system design discussion, perhaps picture yourself navigating those Wessex trails. Are you planning the route carefully? Choosing the right gear (balancing features and fixability)? Selecting the right architectural structure (&amp;ldquo;packing system&amp;rdquo;)? Carrying the right &amp;ldquo;spare parts&amp;rdquo; and building in decoupling for critical failures? Do you know how to navigate when things go off-plan? Are you setting a sustainable pace?&lt;/p&gt;
&lt;p&gt;Thinking like a bike packer might just help you build stronger, more resilient systems ready for whatever the journey throws at them.&lt;/p&gt;
&lt;p&gt;Some photos from our trip:
&lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/StartOfKaw.jpg&#34;
	width=&#34;3264&#34;
	height=&#34;2448&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/StartOfKaw_hu55de0fd4626889cdf84c17040ac9531e_2122729_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/StartOfKaw_hu55de0fd4626889cdf84c17040ac9531e_2122729_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Start&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_081013417.MP.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_081013417.MP_hu50ac916050d61721a4c389a243f6c2e8_5693010_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_081013417.MP_hu50ac916050d61721a4c389a243f6c2e8_5693010_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Image 1&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_083842991.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_083842991_hud00127b2af4a2d2ec9725ac467d91817_4744137_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_083842991_hud00127b2af4a2d2ec9725ac467d91817_4744137_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Image 2&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP_hu50ac916050d61721a4c389a243f6c2e8_8030586_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP_hu50ac916050d61721a4c389a243f6c2e8_8030586_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Cows in the Road!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_114542960.jpg&#34;
	width=&#34;2614&#34;
	height=&#34;3995&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_114542960_hue908ea3f88be9dd4633544e5c59db080_2536401_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_114542960_hue908ea3f88be9dd4633544e5c59db080_2536401_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Cows in the Road!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;65&#34;
		data-flex-basis=&#34;157px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_082821848.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_082821848_hua8864fb00b61292584dbd8faf003d25e_4588754_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_082821848_hua8864fb00b61292584dbd8faf003d25e_4588754_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Cows in the Road!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_084049222.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_084049222_hu940d5f1620762ffc8951be70fa911293_5034865_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210911_084049222_hu940d5f1620762ffc8951be70fa911293_5034865_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Cows in the Road!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP_hu50ac916050d61721a4c389a243f6c2e8_8030586_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_091129290.MP_hu50ac916050d61721a4c389a243f6c2e8_8030586_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Cows in the Road!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt; &lt;img src=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_191951464.jpg&#34;
	width=&#34;4032&#34;
	height=&#34;3024&#34;
	srcset=&#34;https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_191951464_hu9fe32679b8be8af591f5b4b914dcf06c_4289990_480x0_resize_q75_box.jpg 480w, https://michael-dugmore.pages.dev/p/bike-packing-system-design-analogy/images/PXL_20210910_191951464_hu9fe32679b8be8af591f5b4b914dcf06c_4289990_1024x0_resize_q75_box.jpg 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Salisbury Cathedral!&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;133&#34;
		data-flex-basis=&#34;320px&#34;
	
&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Surfing the AI Tsunami: My Pragmatic Plan to Stay Current (Without Drowning)</title>
        <link>https://michael-dugmore.pages.dev/p/ai-dev-learning-pragmatic/</link>
        <pubDate>Mon, 21 Apr 2025 17:30:07 +0100</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/ai-dev-learning-pragmatic/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/ai-dev-learning-pragmatic/ai-wave.jpg" alt="Featured image of post Surfing the AI Tsunami: My Pragmatic Plan to Stay Current (Without Drowning)" /&gt;&lt;p&gt;Okay, let&amp;rsquo;s have a real chat. Does anyone else feel like keeping up with AI developments is less &amp;ldquo;lifelong learning&amp;rdquo; and more like frantically treading water while a giant wave labelled &amp;ldquo;AI Everything, All The Time&amp;rdquo; looms over you? Seriously, blink and there&amp;rsquo;s a new model, a new framework, a new library, some breathless think piece declaring my entire skillset obsolete. It&amp;rsquo;s flat-out &lt;em&gt;exhausting&lt;/em&gt;, and the FOMO is intense.&lt;/p&gt;
&lt;p&gt;I know this cycle personally. I&amp;rsquo;ll see something shiny – maybe the latest paper on Retrieval-Augmented Generation (RAG) or a cool demo of autonomous AI Agents – and my brain instantly goes into overdrive. &amp;ldquo;Whoa, this could totally change how we handle X!&amp;rdquo; or &amp;ldquo;Imagine building Y with this!&amp;rdquo; Next thing I know, it&amp;rsquo;s 11 PM on a Tuesday, I&amp;rsquo;m elbow-deep in a half-baked quickstart, chasing cryptic errors, convinced &lt;em&gt;this&lt;/em&gt; is the silver bullet.&lt;/p&gt;
&lt;p&gt;The initial dopamine hit of &amp;ldquo;figuring it out&amp;rdquo; is great, but then the reality check hits hard. I try to move past &amp;ldquo;hello world,&amp;rdquo; and suddenly I&amp;rsquo;m wrestling with minimal docs, flaky examples, and tech that&amp;rsquo;s clearly still got sharp edges. My evenings and weekends evaporate into debugging sessions (sorry, family!), and often, the main takeaway isn&amp;rsquo;t a cool new feature, but the hard-won, slightly frustrating knowledge that maybe this particular shiny thing isn&amp;rsquo;t quite ready for prime time &lt;em&gt;for my needs&lt;/em&gt;. It’s a cycle that leaves me feeling busy but not necessarily productive, just&amp;hellip; drained.&lt;/p&gt;
&lt;p&gt;This whole experience got me thinking: there &lt;em&gt;has&lt;/em&gt; to be a more sustainable, less frantic way for working developers like us to engage with AI without burning out or constantly feeling like we&amp;rsquo;re falling behind.&lt;/p&gt;
&lt;p&gt;This post is the first in a series where I want to share what I&amp;rsquo;m learning (and frankly, struggling with) as I try to navigate this AI landscape pragmatically. So, how are &lt;em&gt;I&lt;/em&gt; trying to ride this wave without getting completely pummelled? Here are a few strategies I&amp;rsquo;m trying to stick to:&lt;/p&gt;
&lt;h3 id=&#34;1-focus-your-sips-you-cant-drink-the-ocean-anyway&#34;&gt;1. Focus Your Sips: You Can&amp;rsquo;t Drink the Ocean Anyway
&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s just accept it: &lt;strong&gt;we cannot learn it all.&lt;/strong&gt; The firehose of information is too much. Instead of chasing every single new announcement (a habit I&amp;rsquo;m actively trying to break!), I&amp;rsquo;m forcing myself to ask: &amp;ldquo;What &lt;em&gt;actually&lt;/em&gt; helps me or my team build better software, fix real problems, or make my own workflow smoother &lt;em&gt;right now&lt;/em&gt; or in the &lt;em&gt;very&lt;/em&gt; near future?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;For me, right now, that means focusing on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Getting Smarter with My AI Coding Buddy:&lt;/strong&gt; Really mastering GitHub Copilot (or your tool of choice like ChatGPT, Claude, etc.) beyond basic autocompletion. Using it effectively for refactoring, explaining complex code snippets, generating test cases, drafting documentation – essentially, making it a true force multiplier for tasks I already do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrating, Not Inventing:&lt;/strong&gt; Learning how to confidently call existing, relatively mature AI APIs (like Azure OpenAI, OpenAI&amp;rsquo;s API, Anthropic&amp;rsquo;s, Google&amp;rsquo;s, etc.). Adding specific, valuable features like text summarisation, content moderation, semantic search, or maybe a &lt;em&gt;simple&lt;/em&gt;, scoped chatbot where it clearly solves a user need. I don&amp;rsquo;t need to build the Large Language Model (LLM), just use the service effectively and responsibly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Prompt Crafting:&lt;/strong&gt; Realising that getting useful output from an LLM is a skill. I&amp;rsquo;m trying to learn the fundamentals of clear instructions, providing context, and iterating on prompts to get what I actually need, rather than just getting frustrated with vague answers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m deliberately &lt;em&gt;not&lt;/em&gt; trying to become an expert on transformer architectures or the nuances of different quantization techniques right now. That&amp;rsquo;s fascinating stuff, but it&amp;rsquo;s not directly helping me ship code next week. The focus is on practical application using tools stable enough to integrate reliably.&lt;/p&gt;
&lt;h3 id=&#34;2-your-bedrock-solid-engineering-still-rules&#34;&gt;2. Your Bedrock: Solid Engineering Still Rules
&lt;/h3&gt;&lt;p&gt;Amidst all the &amp;ldquo;AI will change everything&amp;rdquo; talk, it&amp;rsquo;s easy to get distracted. But here&amp;rsquo;s the thing: fundamental software engineering principles are &lt;em&gt;more&lt;/em&gt; critical than ever. Clean code, good architecture, automated testing, understanding data structures, system design thinking – this isn&amp;rsquo;t going away.&lt;/p&gt;
&lt;p&gt;In fact, these skills are &lt;em&gt;essential&lt;/em&gt; for integrating AI effectively. An LLM can generate code, sure, but if you don&amp;rsquo;t have the skills to evaluate it, refactor it, test it, and integrate it into a well-structured system, you&amp;rsquo;re just creating tech debt faster. AI tools amplify &lt;em&gt;your&lt;/em&gt; abilities. Using Copilot on a fragile, untested codebase won&amp;rsquo;t magically fix it; it might just help you write more fragile code quicker. Being a strong &lt;em&gt;engineer&lt;/em&gt; who can &lt;em&gt;leverage&lt;/em&gt; AI is way more valuable than being someone who knows AI buzzwords but lacks the foundations. Good engineering is my anchor in this storm.&lt;/p&gt;
&lt;h3 id=&#34;3-learn-slow-and-steady-sustainable-pacing-beats-burnout&#34;&gt;3. Learn Slow and Steady: Sustainable Pacing Beats Burnout
&lt;/h3&gt;&lt;p&gt;That frantic sprint down rabbit holes I mentioned earlier? Total recipe for burnout. I&amp;rsquo;m trying to adopt a more sustainable learning pace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pick &lt;em&gt;One&lt;/em&gt; Thing:&lt;/strong&gt; Seriously, just one small, concrete goal at a time. Maybe this month, I&amp;rsquo;ll dedicate learning time &lt;em&gt;only&lt;/em&gt; to understanding how to effectively use RAG with a specific vector database for a &lt;em&gt;single&lt;/em&gt; use case. Or maybe just focus on getting better at writing prompts for code explanation. Small, focused wins feel achievable and less overwhelming.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timebox Ruthlessly:&lt;/strong&gt; This is key for me. I try to schedule a specific, &lt;em&gt;limited&lt;/em&gt; block of time – maybe 2 hours every other Friday, or 30 minutes a few times a week – dedicated &lt;em&gt;only&lt;/em&gt; to exploring that one chosen AI topic. When the time&amp;rsquo;s up, it&amp;rsquo;s up. This prevents it from bleeding into family time or weekend projects (mostly!).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace &amp;ldquo;I Don&amp;rsquo;t Know Yet&amp;rdquo;:&lt;/strong&gt; Giving myself permission &lt;em&gt;not&lt;/em&gt; to have an immediate opinion or deep knowledge about every new AI paper or tool release has been liberating. It&amp;rsquo;s okay to say &amp;ldquo;That sounds interesting, I&amp;rsquo;ll check it out when/if it becomes relevant to my work.&amp;rdquo; I&amp;rsquo;m trying to wait for the initial hype dust to settle before diving deep into the truly cutting-edge stuff.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;4-build-your-signal-filter-curate-your-inputs&#34;&gt;4. Build Your Signal Filter: Curate Your Inputs
&lt;/h3&gt;&lt;p&gt;My brain has finite bandwidth. I realised I was letting every breathless LinkedIn post or clickbait tech headline hijack my attention and dictate my learning priorities. No more.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aggressively Unfollow:&lt;/strong&gt; If a source consistently feels like hype over substance, or just makes me feel anxious, I hit unfollow. No apologies. My mental energy is too valuable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seek Out Pragmatic Voices:&lt;/strong&gt; I&amp;rsquo;m actively looking for a small handful of bloggers, newsletters, or company tech blogs that offer balanced, practical insights, tutorials, code examples, and &lt;em&gt;realistic&lt;/em&gt; case studies. Bonus points if they openly discuss limitations and challenges.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prioritize &amp;ldquo;How-To&amp;rdquo; Over &amp;ldquo;Hot Take&amp;rdquo;:&lt;/strong&gt; I&amp;rsquo;m trying to favour content that shows &lt;em&gt;how&lt;/em&gt; to actually use a tool or technique in a realistic context, rather than just high-level announcements or opinion pieces about benchmark scores.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;wrapping-up-staying-afloat-staying-pragmatic-and-sane&#34;&gt;Wrapping Up: Staying Afloat, Staying Pragmatic (and Sane)
&lt;/h3&gt;&lt;p&gt;This AI wave is undeniably powerful, and yes, it &lt;em&gt;is&lt;/em&gt; changing aspects of our work. But freaking out and trying to master everything overnight isn&amp;rsquo;t the answer (at least not for me). You don&amp;rsquo;t need to be a theoretical physicist to use electricity, and you don&amp;rsquo;t need to be an AI researcher building foundational models to leverage AI effectively in your development workflow.&lt;/p&gt;
&lt;p&gt;My plan? Filter the relentless noise, focus on how AI can &lt;em&gt;practically&lt;/em&gt; help me and my team solve problems &lt;em&gt;today&lt;/em&gt;, keep sharpening those core engineering skills, learn at a sustainable pace that respects my time and energy, and be incredibly picky about my information diet.&lt;/p&gt;
&lt;p&gt;By trying to stay pragmatic, I hope to navigate this AI tsunami, harness its power where it truly makes sense, and crucially, avoid drowning in the hype (and maybe, just maybe, reclaim some evenings!). Keep coding, keep learning (sensibly!), and let&amp;rsquo;s figure this out together.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This is just my current approach, born from my own struggles with AI overwhelm. Technology choices are always context-dependent. I&amp;rsquo;m really keen to hear how &lt;em&gt;you&lt;/em&gt; are managing this! What strategies are working for you? What tools are genuinely useful? Drop a comment below! Look out for future posts where I&amp;rsquo;ll dive deeper into specific tools and techniques I&amp;rsquo;m experimenting with.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;resources-im-finding-useful-for-now&#34;&gt;Resources I&amp;rsquo;m Finding Useful (for now!):
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI Coding Assistants &amp;amp; Prompting:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Copilot Docs:&lt;/strong&gt; Good starting point for understanding its capabilities beyond basic autocomplete. &lt;a class=&#34;link&#34; href=&#34;https://docs.github.com/en/copilot&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://docs.github.com/en/copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI Prompting Guide:&lt;/strong&gt; Offers concrete techniques for getting better results from models like GPT-4, applicable beyond just OpenAI. &lt;a class=&#34;link&#34; href=&#34;https://platform.openai.com/docs/guides/prompt-engineering&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://platform.openai.com/docs/guides/prompt-engineering&lt;/a&gt; (Or check out guides from Anthropic/Google too!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploring AI APIs:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenAI API Cookbook:&lt;/strong&gt; Lots of practical code examples for common tasks using their API. &lt;a class=&#34;link&#34; href=&#34;https://cookbook.openai.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://cookbook.openai.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure AI Services Documentation:&lt;/strong&gt; Comprehensive info if you&amp;rsquo;re in the Microsoft ecosystem. &lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/azure/ai-services/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/azure/ai-services/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staying Updated (Sensibly):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TLDR AI Newsletter:&lt;/strong&gt; My go-to for quick, daily summaries. Helps filter the noise. &lt;a class=&#34;link&#34; href=&#34;https://tldr.tech/ai&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://tldr.tech/ai&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Ditching MediatR? Maybe a Simple CQRS Dispatcher is All You Need</title>
        <link>https://michael-dugmore.pages.dev/p/mediatr/</link>
        <pubDate>Sat, 19 Apr 2025 10:44:07 +0100</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/mediatr/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/mediatr/cqrs-dispatch.jpg" alt="Featured image of post Ditching MediatR? Maybe a Simple CQRS Dispatcher is All You Need" /&gt;&lt;p&gt;Alright, let&amp;rsquo;s talk about MediatR. You&amp;rsquo;ve probably heard the news – the popular .NET library, often the go-to for CQRS-style patterns, is shifting towards a commercial license for commercial use. Now, fair play to the maintainer for sustainability, but it definitely got me thinking. Do we &lt;em&gt;really&lt;/em&gt; need a library like MediatR for decoupling our commands and queries? Often, the answer is simpler than you think.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t just about licensing costs; it&amp;rsquo;s a good chance to look at the complexity we sometimes invite into our codebases.&lt;/p&gt;
&lt;h3 id=&#34;that-time-mediatr-became-the-maze-runner&#34;&gt;That Time MediatR Became the Maze Runner
&lt;/h3&gt;&lt;p&gt;I remember one project vividly. It started clean, using MediatR to nicely separate commands/queries from handlers. Standard stuff. But over time, it grew&amp;hellip; wild. We had MediatR requests triggering other MediatR requests, sometimes nested. Notifications were flying everywhere, kicking off background jobs or updating read models in ways that weren&amp;rsquo;t immediately obvious.&lt;/p&gt;
&lt;p&gt;Debugging became a nightmare. Trying to trace a single user action felt like navigating a maze. Where did the request &lt;em&gt;actually&lt;/em&gt; go? Which notification handlers fired? Were they essential, or just side effects added later? We spent &lt;em&gt;so much time&lt;/em&gt; just trying to understand the flow, time that could have been spent building features. It wasn&amp;rsquo;t MediatR&amp;rsquo;s fault &lt;em&gt;per se&lt;/em&gt;, but the perceived &amp;ldquo;magic&amp;rdquo; it offered made it too easy to create tangled dependencies and obscure the core logic. It highlighted a big downside: when things get complex, that layer of abstraction can hinder understanding rather than help.&lt;/p&gt;
&lt;h3 id=&#34;quick-refresher-cqrs--mediatr&#34;&gt;Quick Refresher: CQRS != MediatR
&lt;/h3&gt;&lt;p&gt;Just to be clear: CQRS (Command Query Responsibility Segregation) is a &lt;em&gt;pattern&lt;/em&gt;. It&amp;rsquo;s about separating your write operations (Commands) from your read operations (Queries). MediatR is just &lt;em&gt;one tool&lt;/em&gt; you can use to implement the dispatching part of that pattern. You absolutely don&amp;rsquo;t &lt;em&gt;need&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;The core idea is simple: send a message (command/query) and have the right piece of code handle it. We can totally do that ourselves.&lt;/p&gt;
&lt;h3 id=&#34;rolling-your-own-a-simple-dispatcher-pattern&#34;&gt;Rolling Your Own: A Simple Dispatcher Pattern
&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s sketch out a lean, mean CQRS dispatcher using basic C# and the built-in dependency injection container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Define Your Messages (Plain Old C# Objects)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Commands and Queries are just data carriers. Records are great for this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Command: Do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;record&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;CreateProductCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;decimal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Query: Ask for something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;record&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;GetProductByIdQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Guid&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Query Result: The data you get back&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;record&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Guid&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;decimal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. Define Your Handlers (Interfaces + Classes)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Simple interfaces make it clear what handles what.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Handles commands (no return value typically)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;interface&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ICommandHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Handles queries (returns a result)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;interface&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;IQueryHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And the implementations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Example Command Handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;CreateProductCommandHandler&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ICommandHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CreateProductCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Inject your DbContext, repositories, services here...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CreateProductCommand&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WriteLine&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;$&amp;#34;Creating product: {command.Name}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ... your actual logic to save the product&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CompletedTask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Placeholder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Example Query Handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;GetProductByIdQueryHandler&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IQueryHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetProductByIdQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Inject your read-specific dependencies...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetProductByIdQuery&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WriteLine&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;$&amp;#34;Fetching product: {query.Id}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ... your actual logic to fetch the product&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Example return:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FromResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Sample Product&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;19.99&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;3. The Dispatcher: Connecting the Dots&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the heart of it. A simple service that uses the DI container to find and run the right handler.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;interface&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;IDispatcher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SendAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;notnull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;QueryAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;notnull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;DependencyInjectionDispatcher&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IDispatcher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IServiceProvider&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_serviceProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;DependencyInjectionDispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IServiceProvider&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;serviceProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_serviceProvider&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;serviceProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SendAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;notnull&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Find the right command handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;handler&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_serviceProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetRequiredService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ICommandHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TCommand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;QueryAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CancellationToken&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;notnull&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Find the right query handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;handler&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_serviceProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetRequiredService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IQueryHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HandleAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cancellationToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;4. Wire it Up (Dependency Injection)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Register your handlers and the dispatcher. Using a library like &lt;code&gt;Scrutor&lt;/code&gt; makes assembly scanning easy, but you can register them manually too.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// In Program.cs or Startup.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Example using Scrutor for scanning:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Scan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;scan&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;scan&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FromAssemblies&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Assembly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Or specify your assembly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddClasses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classes&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;classes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AssignableTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;typeof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ICommandHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&amp;gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AsImplementedInterfaces&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WithScopedLifetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Or Transient/Singleton as needed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddClasses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classes&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;classes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AssignableTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;typeof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IQueryHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;,&amp;gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AsImplementedInterfaces&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WithScopedLifetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;());&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Or Transient/Singleton as needed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Register the dispatcher itself&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddScoped&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IDispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;DependencyInjectionDispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// If not using scanning, you&amp;#39;d register each handler manually:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// services.AddScoped&amp;lt;ICommandHandler&amp;lt;CreateProductCommand&amp;gt;, CreateProductCommandHandler&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// services.AddScoped&amp;lt;IQueryHandler&amp;lt;GetProductByIdQuery, ProductDto&amp;gt;, GetProductByIdQueryHandler&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ...etc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;5. Use It!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inject &lt;code&gt;IDispatcher&lt;/code&gt; wherever you need it (like your API controllers).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ProductsController&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ControllerBase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IDispatcher&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_dispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductsController&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IDispatcher&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_dispatcher&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IActionResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FromBody&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CreateProductCommand&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_dispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SendAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Consider returning a 201 Created with location header&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;    [HttpGet(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ActionResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Guid&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetProductByIdQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_dispatcher&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;QueryAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetProductByIdQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NotFound&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;why-bother-the-payoff&#34;&gt;Why Bother? The Payoff
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Extra Dependency:&lt;/strong&gt; One less library to worry about, manage, or pay for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crystal Clear Flow:&lt;/strong&gt; You know exactly how a command/query gets to its handler. Debugging is just stepping through your own code. &lt;code&gt;F12&lt;/code&gt; (Go To Definition) on &lt;code&gt;SendAsync&lt;/code&gt; or &lt;code&gt;QueryAsync&lt;/code&gt; takes you straight to your dispatcher, not library internals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total Control:&lt;/strong&gt; Want logging, validation, or transactions around your handlers? Implement it yourself using decorators or simple middleware logic in your dispatcher. No need to learn a library-specific pipeline model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt; It keeps the focus on the CQRS pattern itself, not on the tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-about-mediatrs-bells-and-whistles&#34;&gt;What About MediatR&amp;rsquo;s Bells and Whistles?
&lt;/h3&gt;&lt;p&gt;Now, let&amp;rsquo;s be clear: MediatR offers more than just simple dispatching, and its other features are often where its perceived value lies, especially for complex applications. It&amp;rsquo;s worth acknowledging these, particularly the point often raised about handling cross-cutting concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipelines (Behaviors):&lt;/strong&gt; This is arguably MediatR&amp;rsquo;s killer feature for many. It provides a clean, built-in mechanism (&lt;code&gt;IPipelineBehavior&amp;lt;TRequest, TResponse&amp;gt;&lt;/code&gt;) to implement cross-cutting concerns – think logging, validation, transaction management, security checks, caching – that need to wrap around your command and query handlers. It&amp;rsquo;s a structured way to add logic &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;after&lt;/em&gt; the actual handler runs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The DIY Alternative:&lt;/strong&gt; As mentioned, you &lt;em&gt;can&lt;/em&gt; replicate this using the Decorator pattern combined with your Dependency Injection container. You&amp;rsquo;d create specific decorator classes (e.g., &lt;code&gt;ValidationCommandHandlerDecorator&amp;lt;TCommand&amp;gt;&lt;/code&gt;, &lt;code&gt;TransactionCommandHandlerDecorator&amp;lt;TCommand&amp;gt;&lt;/code&gt;) and configure your DI setup to apply them in the desired order, effectively building your own pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Trade-Off:&lt;/strong&gt; While the decorator approach gives you maximum transparency and control (you own the code, no library &amp;lsquo;magic&amp;rsquo;), &lt;strong&gt;let&amp;rsquo;s be honest:&lt;/strong&gt; setting up and managing a robust, ordered pipeline with decorators manually requires significantly more upfront configuration and boilerplate code compared to leveraging MediatR&amp;rsquo;s pipeline feature. The convenience MediatR offers here is undeniable and a major reason teams adopt it. It&amp;rsquo;s a crucial point to weigh against the desire for fewer dependencies and ultimate explicitness.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Notifications:&lt;/strong&gt; MediatR provides a publish/subscribe mechanism for &amp;ldquo;fire-and-forget&amp;rdquo; events where multiple independent handlers might need to react (e.g., &lt;code&gt;OrderCreated&lt;/code&gt; event triggering email sending, inventory update, etc.).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The DIY Alternative:&lt;/strong&gt; For this, you can often implement a simpler observer pattern yourself. Create a dedicated &lt;code&gt;IEventDispatcher&lt;/code&gt; (or similar) that resolves &lt;code&gt;IEnumerable&amp;lt;IEventHandler&amp;lt;TEvent&amp;gt;&amp;gt;&lt;/code&gt; from your DI container and invokes all registered handlers. For more complex scenarios needing persistence, outbox patterns, or distributed messaging, integrating dedicated libraries like MassTransit or Rebus is often a better, more focused approach anyway, rather than relying solely on MediatR&amp;rsquo;s in-process notifications. Separating eventing from command/query dispatch can often lead to clearer boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The key takeaway here isn&amp;rsquo;t necessarily that these features aren&amp;rsquo;t valuable, but whether the &lt;em&gt;convenience&lt;/em&gt; they offer outweighs the potential complexity, dependency management, and now licensing considerations introduced by the library, &lt;em&gt;especially&lt;/em&gt; if your primary need is just the core command/query dispatch.&lt;/strong&gt; If you heavily rely on and benefit from the pipeline behaviors, MediatR might still be the right tool for you, but it&amp;rsquo;s worth understanding the alternatives and the trade-offs involved.&lt;/p&gt;
&lt;h3 id=&#34;wrapping-up&#34;&gt;Wrapping Up
&lt;/h3&gt;&lt;p&gt;Look, MediatR has been useful for many, but this licensing change is a great nudge to question our dependencies. For the core job of dispatching commands and queries in a CQRS setup, a simple, home-rolled approach using standard .NET features is often more than enough.&lt;/p&gt;
&lt;p&gt;It gives you back control, simplifies your stack, and avoids potential future costs or licensing headaches. Before automatically reaching for MediatR (or any library), ask yourself: could a few interfaces and a simple dispatcher class do the job? You might be surprised how often the answer is yes.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: The views expressed in this post are my own, based on my experiences and observations. Technology choices are often context-dependent, and I welcome different perspectives or challenges to my points in the comments below!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;further-reading&#34;&gt;Further Reading
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CQRS Pattern:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Learn - CQRS Pattern: &lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TechTarget - What is CQRS: &lt;a class=&#34;link&#34; href=&#34;https://www.techtarget.com/searchapparchitecture/definition/CQRS-Command-Query-Responsibility-Segregation&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.techtarget.com/searchapparchitecture/definition/CQRS-Command-Query-Responsibility-Segregation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Injection in .NET:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Learn - Dependency Injection in .NET: &lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Microsoft Learn - Dependency Injection Guidelines: &lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regarding MediatR Licensing (for context):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Jimmy Bogard&amp;rsquo;s blog post discussing the MediatR dual license model (search for relevant post): &lt;a class=&#34;link&#34; href=&#34;https://jimmybogard.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://jimmybogard.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Beyond the Magic: Is AutoMapper Doing More Harm Than Good? (And Why Now&#39;s a Good Time to Re-evaluate)</title>
        <link>https://michael-dugmore.pages.dev/p/automapper/</link>
        <pubDate>Thu, 17 Apr 2025 10:00:07 +0100</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/p/automapper/</guid>
        <description>&lt;img src="https://michael-dugmore.pages.dev/p/automapper/automapper.jpg" alt="Featured image of post Beyond the Magic: Is AutoMapper Doing More Harm Than Good? (And Why Now&#39;s a Good Time to Re-evaluate)" /&gt;&lt;p&gt;For years, AutoMapper has been almost a default choice for handling the tedious chore of mapping objects in .NET. Got an entity you need as a DTO? AutoMapper. Need to turn a command into an entity? AutoMapper again. It promised to slay the dragon of boilerplate mapping code, and honestly, for simple stuff, it felt pretty good.&lt;/p&gt;
&lt;p&gt;But things change. AutoMapper&amp;rsquo;s creator recently announced that, like MediatR, newer versions are moving to a dual-license model (AGPL open-source, or a paid commercial license – you can read the official announcement details &lt;a class=&#34;link&#34; href=&#34;https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;here&lt;/a&gt;). While that&amp;rsquo;s understandable for keeping a project alive, it&amp;rsquo;s also a perfect excuse for us to step back and ask: is AutoMapper &lt;em&gt;really&lt;/em&gt; the win we think it is, or does its &amp;ldquo;magic&amp;rdquo; sometimes cause more trouble than it&amp;rsquo;s worth?&lt;/p&gt;
&lt;p&gt;I want to argue here that despite its massive popularity, AutoMapper often comes with significant downsides, especially as applications grow. The licensing change isn&amp;rsquo;t just about cost; it&amp;rsquo;s a nudge to finally confront the headaches its &amp;ldquo;magic&amp;rdquo; can create.&lt;/p&gt;
&lt;h2 id=&#34;the-original-sin-why-we-fell-for-it&#34;&gt;The Original Sin: Why We Fell for It
&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s be fair – nobody &lt;em&gt;likes&lt;/em&gt; writing repetitive mapping code. Manually assigning properties between dozens of classes is soul-crushing. AutoMapper&amp;rsquo;s convention-based approach (if it looks the same, map it!) felt like a huge productivity boost early on.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// The Old Way (Yawn):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;orderDto&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OrderDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;OrderId&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;CustomerName&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Customer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Simple enough...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;TotalAmount&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Sum&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Quantity&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Okay, a bit more involved&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// The AutoMapper Way (Looks clean!):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Configure the map once... then just:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;orderDto&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_mapper&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OrderDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That second version looks way slicker, right? That slickness is the hook. But the magic has a dark side.&lt;/p&gt;
&lt;h2 id=&#34;the-pitfalls-when-magic-turns-into-a-curse&#34;&gt;The Pitfalls: When Magic Turns into a Curse
&lt;/h2&gt;&lt;p&gt;Simple, one-to-one mappings are AutoMapper&amp;rsquo;s sweet spot. But real-world code is rarely just that simple. That&amp;rsquo;s where things get hairy.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Black Box Problem:&lt;/strong&gt; This is the big one. You see &lt;code&gt;_mapper.Map&amp;lt;OrderDto&amp;gt;(order)&lt;/code&gt;, but what&amp;rsquo;s &lt;em&gt;actually&lt;/em&gt; happening? You have no idea without spelunking through AutoMapper profiles.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is &lt;code&gt;CustomerName&lt;/code&gt; a direct map, or is some hidden custom resolver messing with it?&lt;/li&gt;
&lt;li&gt;How did &lt;code&gt;TotalAmount&lt;/code&gt; get calculated? Did AutoMapper magically run a &lt;code&gt;Sum&lt;/code&gt; because of some &lt;code&gt;ForMember&lt;/code&gt; rule buried in config?&lt;/li&gt;
&lt;li&gt;What if property names are &lt;em&gt;close&lt;/em&gt; but not identical? Will it fail silently? Throw a cryptic error? Who knows!
This opacity makes the code hard to read, reason about, and trust compared to just seeing the assignments directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debugging Nightmares:&lt;/strong&gt; Ever tried figuring out why a mapped property is &lt;code&gt;null&lt;/code&gt; or has a weird value when using AutoMapper? It can be painful. You can&amp;rsquo;t just step through the mapping line by line like normal code. You&amp;rsquo;re stuck trying to decipher AutoMapper&amp;rsquo;s internals, its configuration jungle, and how its resolvers decided to execute. The error messages often point fingers at the configuration itself, not the root data or logic issue.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;My &amp;ldquo;Aha!&amp;rdquo; Moment (the bad kind):&lt;/strong&gt; I remember inheriting a project years ago where AutoMapper was &lt;em&gt;everywhere&lt;/em&gt;. We hit a bug where a crucial financial field in a DTO was subtly wrong under specific conditions. Tracking it down was absolute agony. It wasn&amp;rsquo;t a direct mapping; it involved a chain of &lt;code&gt;Profile&lt;/code&gt; configurations, a custom &lt;code&gt;ValueResolver&lt;/code&gt; that called &lt;em&gt;another&lt;/em&gt; service, and some &lt;code&gt;ForMember&lt;/code&gt; conditions that weren&amp;rsquo;t immediately obvious. We spent &lt;em&gt;hours&lt;/em&gt; digging through layers of AutoMapper configuration just to understand the path the data was taking. When we finally found the logic flaw buried in that resolver, the fix itself was trivial, but the diagnostic process was brutal. That was the point I realized the &amp;ldquo;magic&amp;rdquo; wasn&amp;rsquo;t worth the obfuscation – a clear, explicit mapping method, even if longer, would have saved us hours of frustration and made the bug obvious much sooner.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration Bloat:&lt;/strong&gt; As your mappings get more complex (conditionals, ignored fields, custom converters, nested stuff), the AutoMapper configuration code balloons. Those &lt;code&gt;Profile&lt;/code&gt; classes become sprawling monsters. Suddenly, the boilerplate you tried to kill in your mapping code has mutated and reappeared in your configuration, often in a format that&amp;rsquo;s harder to read and maintain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Hits (Especially Startup):&lt;/strong&gt; AutoMapper leans heavily on reflection, especially when your app first starts and it&amp;rsquo;s figuring out all the mapping plans. It tries to compile things for runtime speed, but that initial startup hit can be noticeable, especially with lots of maps. Plus, using &lt;code&gt;ProjectTo&lt;/code&gt; with database queries (like EF Core) can generate some seriously complex expression trees that might not perform as you expect if you&amp;rsquo;re not careful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dependency Lock-in:&lt;/strong&gt; Pulling in AutoMapper means pulling in a hefty dependency. Upgrading it can sometimes bring breaking changes. And if you want it to play nice with things like Entity Framework&amp;rsquo;s query projection, you often need &lt;em&gt;more&lt;/em&gt; specific AutoMapper extension packages, tying your codebase even tighter to its ecosystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sometimes, Simple is Just&amp;hellip; Simpler:&lt;/strong&gt; Honestly, for many basic mappings, just writing it out manually is &lt;em&gt;clearer&lt;/em&gt; and faster to understand than messing with AutoMapper config. &lt;code&gt;new TargetDto { PropA = source.PropB, PropC = source.PropD }&lt;/code&gt; needs no explanation. Any C# dev gets it instantly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The New Price Tag:&lt;/strong&gt; Even if you shrug off the technical issues, the commercial license for new versions adds a very practical reason to look elsewhere. Why pay for a tool that might already be adding friction to your development process?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;the-alternatives-clear-readable-fast-mapping&#34;&gt;The Alternatives: Clear, Readable, Fast Mapping
&lt;/h2&gt;&lt;p&gt;Good news! Ditching AutoMapper doesn&amp;rsquo;t mean returning to the dark ages of manual mapping for &lt;em&gt;everything&lt;/em&gt;. We have great alternatives using plain C# or modern tooling.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Good Old Manual Mapping:&lt;/strong&gt; Often the best choice for clarity.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Property Initializers:&lt;/strong&gt; Simple, direct, and obvious.
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MapProduct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ThrowIfNull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Basic guard clause&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Explicit, handles potential null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// What you see is what you get!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static Factory Methods:&lt;/strong&gt; Keep mapping logic contained within the target type.
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Guid&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;decimal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FromProduct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;n&#34;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ThrowIfNull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Usage: var dto = ProductDto.FromProduct(product);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension Methods:&lt;/strong&gt; Make mapping feel fluid.
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ProductMappingExtensions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ToDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;n&#34;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ThrowIfNull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Usage: var dto = product.ToDto();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These methods put the logic right where you can see it, debug it easily, and refactor it with standard tools. For most straightforward cases, this is my go-to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LINQ &lt;code&gt;Select&lt;/code&gt; for Collections:&lt;/strong&gt; When you&amp;rsquo;re mapping lists or query results, &lt;code&gt;Select&lt;/code&gt; is your friend. It&amp;rsquo;s the idiomatic .NET way, especially powerful with &lt;code&gt;IQueryable&lt;/code&gt; (like EF Core) because it often translates directly to SQL.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Map a list in memory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IEnumerable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MapProducts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IEnumerable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ThrowIfNull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Use the extension method for consistency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Select&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ToDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ToList&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Project directly from a database query (EF Core example)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetProductDtosAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IQueryable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ThrowIfNull&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Select directly into the DTO - EF translates this!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Select&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Assuming Price is non-nullable in DB or handled by EF config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ToListAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Execute the query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Readable, efficient, and leverages the power of LINQ and your ORM.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Source Generators (The Modern Approach):&lt;/strong&gt; C# Source Generators are a game-changer here. They let tools generate code &lt;em&gt;at compile time&lt;/em&gt;. Libraries like &lt;strong&gt;Mapster&lt;/strong&gt; use this brilliantly for mapping. You give them hints (often with attributes or simple interfaces), and they write the fast, explicit mapping code &lt;em&gt;for you&lt;/em&gt; during the build.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No runtime reflection overhead.&lt;/li&gt;
&lt;li&gt;Performance like manual mapping.&lt;/li&gt;
&lt;li&gt;Boilerplate reduction like AutoMapper.&lt;/li&gt;
&lt;li&gt;Compile-time safety.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Example using Mapster attributes (simplified setup)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Requires Mapster.Tool and Mapster.Attributes packages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Tell Mapster to generate mappers between these types globally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;[assembly: GenerateMapper(typeof(Product), typeof(ProductDto))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ProductDto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Guid&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;decimal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Price&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Mapster handles simple matching props by convention.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Customizations via attributes or fluent API needed for complex cases.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ... later in code ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Assumes Mapster DI setup or direct usage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dto&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Adapt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProductDto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;();&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Fast, generated code runs here!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Mapster (and similar libraries) feels like AutoMapper&amp;rsquo;s convenience without the runtime mystery and overhead. It&amp;rsquo;s a really compelling option once you wrap your head around source generation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;how-to-escape-a-step-by-step-retreat&#34;&gt;How to Escape: A Step-by-Step Retreat
&lt;/h2&gt;&lt;p&gt;You don&amp;rsquo;t need to rip AutoMapper out overnight. Try a phased withdrawal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;New Code Ban:&lt;/strong&gt; Stop using AutoMapper for any &lt;em&gt;new&lt;/em&gt; mappings. Use manual code, LINQ &lt;code&gt;Select&lt;/code&gt;, or a source generator like Mapster instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Triage and Attack:&lt;/strong&gt; Find your most complex, annoying, or performance-sensitive AutoMapper configurations. Refactor those first using one of the alternatives. Get the biggest wins early.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gradual Refactoring:&lt;/strong&gt; As you touch other parts of the codebase, refactor any AutoMapper usages you encounter. Chip away at it during regular work or dedicated refactoring time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove the Package:&lt;/strong&gt; Once no code references AutoMapper anymore, uninstall the NuGet package. Victory!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping Up
&lt;/h2&gt;&lt;p&gt;AutoMapper promised simplicity, and for a while, it seemed to deliver. But its &amp;ldquo;magic&amp;rdquo; often comes at the cost of clarity, debuggability, and maintainability, especially in complex projects. The upcoming licensing change is just the final nudge to reconsider if this dependency is truly serving you well.&lt;/p&gt;
&lt;p&gt;By favoring clear, explicit C# for simple maps, using LINQ &lt;code&gt;Select&lt;/code&gt; naturally for collections and projections, and exploring powerful source generators like Mapster for the heavy lifting, you can build mapping strategies that are easier to understand, debug, and maintain – all without the hidden costs and potential licensing fees of AutoMapper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And let&amp;rsquo;s be real&lt;/strong&gt;, in today&amp;rsquo;s world where no blog post is complete without mentioning AI &lt;em&gt;somewhere&lt;/em&gt;, these new coding assistants are getting scarily good. Tools like GitHub Copilot or Cursor practically write boilerplate mapping code for you with a simple prompt. If the main appeal of AutoMapper was saving keystrokes on simple mappings, AI tools are eating that lunch pretty effectively now, giving us yet another reason to stick with clearer, explicit code.&lt;/p&gt;
&lt;p&gt;Maybe it&amp;rsquo;s time &lt;em&gt;you&lt;/em&gt; evaluated if the convenience is really worth it anymore.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: The views expressed in this post are my own, based on my experiences and observations. Technology choices are often context-dependent, and I welcome different perspectives or challenges to my points in the comments below!&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;further-reading&#34;&gt;Further Reading
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Regarding MediatR Licensing (for context):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Jimmy Bogard&amp;rsquo;s blog post discussing the MediatR dual license model: &lt;a class=&#34;link&#34; href=&#34;https://jimmybogard.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AutoMapper Official Documentation:&lt;/strong&gt; Even if you&amp;rsquo;re moving away, understanding the official documentation can help when refactoring existing code or understanding its features more deeply.
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://docs.automapper.org/en/latest/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://docs.automapper.org/en/latest/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapster GitHub Repository &amp;amp; Wiki:&lt;/strong&gt; For exploring the primary source generator alternative mentioned. The wiki often contains usage guides and examples.
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/MapsterMapper/Mapster&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/MapsterMapper/Mapster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C# Source Generators Introduction (Microsoft Docs):&lt;/strong&gt; To understand the underlying technology behind tools like Mapster.
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LINQ &lt;code&gt;Select&lt;/code&gt; Documentation (Microsoft Docs):&lt;/strong&gt; Official details on the standard LINQ method for projection.
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity Framework Core Querying Basics (Microsoft Docs):&lt;/strong&gt; Relevant for understanding how LINQ &lt;code&gt;Select&lt;/code&gt; translates to database queries, which is often a key mapping scenario.
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/ef/core/querying/basic&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/ef/core/querying/basic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>Archives</title>
        <link>https://michael-dugmore.pages.dev/archives/</link>
        <pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/archives/</guid>
        <description></description>
        </item>
        <item>
        <title>Links</title>
        <link>https://michael-dugmore.pages.dev/links/</link>
        <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/links/</guid>
        <description>&lt;p&gt;Some quick places to find me and follow along.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Search</title>
        <link>https://michael-dugmore.pages.dev/search/</link>
        <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
        
        <guid>https://michael-dugmore.pages.dev/search/</guid>
        <description></description>
        </item>
        
    </channel>
</rss>
