<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Well Tempered .NET]]></title><description><![CDATA[Some notes about writing software with insight and restraint]]></description><link>https://www.well-tempered.net</link><image><url>https://substackcdn.com/image/fetch/$s_!t0mk!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6deb9724-48f5-4cbb-a588-53732d0f8798_258x258.png</url><title>Well Tempered .NET</title><link>https://www.well-tempered.net</link></image><generator>Substack</generator><lastBuildDate>Tue, 07 Apr 2026 08:55:37 GMT</lastBuildDate><atom:link href="https://www.well-tempered.net/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Alexandru Boia]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[welltemperednet@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[welltemperednet@substack.com]]></itunes:email><itunes:name><![CDATA[Alexandru Boia]]></itunes:name></itunes:owner><itunes:author><![CDATA[Alexandru Boia]]></itunes:author><googleplay:owner><![CDATA[welltemperednet@substack.com]]></googleplay:owner><googleplay:email><![CDATA[welltemperednet@substack.com]]></googleplay:email><googleplay:author><![CDATA[Alexandru Boia]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The automatic typewriter]]></title><description><![CDATA[Automate reading simple POCOs from command line. And by simple I mean really simple]]></description><link>https://www.well-tempered.net/p/the-automatic-typewriter</link><guid isPermaLink="false">https://www.well-tempered.net/p/the-automatic-typewriter</guid><dc:creator><![CDATA[Alexandru Boia]]></dc:creator><pubDate>Sat, 21 Feb 2026 16:08:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!y9EH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At some point, as a funky little exercise (a Code Kata if you will), I wrote a small utility that automates reading object properties from the keyboard, provided the object has a predictable structure: simple enough, fairly strict, and reasonably standardized (primitively typed properties or collections thereof).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y9EH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y9EH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 424w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 848w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y9EH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg" width="1456" height="689" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:689,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2299455,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.well-tempered.net/i/188557809?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!y9EH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 424w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 848w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!y9EH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf0d1e3d-5e0e-439f-8186-887fd2869cfe_4032x1908.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>By &#8220;automation&#8221; I mean this: instead of hand-writing code that reads each property value, I wanted to be able to say: <em>&#8220;Please read an object of type </em><code>T</code><em>, and hand me a newly created instance, will you, Jeeves?&#8221;</em></p><p>More recently, what used to be a passing distraction turned out to be genuinely useful: I needed a manual testing harness for a system that receives well-defined, simple contracts. </p><p>Part of the routine was feeding values by hand and observing the system&#8217;s behavior immediately (basically a service that feeds real time data needs to be restarted in a so-called test mode and see how it reacts with well known payloads).</p><p>That was a good excuse to dust off the old class, take it to the gym, and integrate it properly. After shedding a few skins (and some rough edges), it became a small library with a couple of supporting classes. The end result looks roughly like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1c0a1c3e-70d8-4dc1-9a57-0359fbbf9d4f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">PocoCommandLineReader reader = new PocoCommandLineReader();
CustomerSummary obj = reader.Read("Read customer summary");</code></pre></div><p>Compared to the original version, I added two things:</p><ul><li><p>the ability to read <strong>collections of values</strong>, not just scalars;</p></li><li><p><strong>colored prompts</strong>, <a href="https://github.com/raminrahimzada/cConsole">using a small console formatting helper</a> (ever so slightly modified and renamed).</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yncu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yncu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 424w, https://substackcdn.com/image/fetch/$s_!yncu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 848w, https://substackcdn.com/image/fetch/$s_!yncu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 1272w, https://substackcdn.com/image/fetch/$s_!yncu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yncu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif" width="1381" height="626" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:626,&quot;width&quot;:1381,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:77065,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.well-tempered.net/i/188557809?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yncu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 424w, https://substackcdn.com/image/fetch/$s_!yncu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 848w, https://substackcdn.com/image/fetch/$s_!yncu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 1272w, https://substackcdn.com/image/fetch/$s_!yncu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ec7bcc8-af09-4318-8b65-36fdd95546ab_1381x626.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The rules for eligible types are discouragingly simple:</p><ul><li><p>the target must be a <strong>non-abstract class</strong> (not a struct/value type) and must have a <strong>public parameter-less constructor</strong>;</p></li><li><p>the reader scans <strong>public instance properties</strong> that are both readable and writable (non-static), following the inheritance chain (this behavior is not configurable);</p></li><li><p>supported scalar types: <code>int</code>, <code>uint</code>, <code>short</code>, <code>ushort</code>, <code>long</code>, <code>ulong</code>, <code>string</code>, <code>bool</code>, <code>float</code>, <code>double</code>, <code>decimal</code>, <code>char</code>, <code>byte</code>, <code>sbyte</code>, <code>Guid</code>;</p></li><li><p>supported collection types (elements must be one of the scalar types above): arrays (e.g. <code>int[]</code>) and lists (e.g. <code>IList&lt;int&gt;</code> or <code>List&lt;decimal&gt;</code>);</p></li><li><p>any property whose type cannot be handled is simply ignored;</p></li><li><p>default values for scalar properties are whatever the object constructor produces (i.e. the CLR defaults unless you initialize differently);</p></li><li><p>for collections, the user is asked for the number of elements upfront;</p></li><li><p>optionally, you can decorate properties with <code>System.ComponentModel.DescriptionAttribute</code> to customize the prompt message;</p></li><li><p>if the user input cannot be converted to the target type, the reader does not throw&#8212;it falls back to the default value for that type.</p></li></ul><p>Most conversions are straightforward (<code>int.TryParse()</code>, etc.), with a couple of notable exceptions:</p><ul><li><p>for <code>bool</code>, the reader also understands &#8220;yes&#8221;, &#8220;da&#8221;, &#8220;1&#8221; as <code>true</code>, and &#8220;no&#8221;, &#8220;nu&#8221;, &#8220;0&#8221; as <code>false</code>;</p></li><li><p>for <code>Guid</code>, you can type <code>new()</code> to generate a new <code>GUID </code>(the input value itself doesn&#8217;t matter as long as it signals the intent).</p></li></ul><p>There is nothing particularly difficult here, merely a handful of subtleties. But I was presumptuous enough to think it might be useful to others, <a href="https://github.com/alexboia/CSharp-PocoCommandLineReader/">so I published it on GitHub</a>. </p><p>It&#8217;s not a revolutionary piece of engineering, just a small tool that does one thing predictably. If you find it useful, make a grab for it. If flaws are to be found, I&#8217;m sure someone will let me know.</p>]]></content:encoded></item><item><title><![CDATA[I know what it is, but what does it mean?]]></title><description><![CDATA[When infrastructure leaks into your domain and how to restore the boundary]]></description><link>https://www.well-tempered.net/p/i-know-what-it-is-but-what-does-it</link><guid isPermaLink="false">https://www.well-tempered.net/p/i-know-what-it-is-but-what-does-it</guid><dc:creator><![CDATA[Alexandru Boia]]></dc:creator><pubDate>Thu, 19 Feb 2026 22:47:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1mVE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>First, let us lay out the coordinates of the discussion. This is about the fairly common situation where data access is abstracted away:</p><ul><li><p>behind a <a href="https://martinfowler.com/eaaCatalog/domainModel.html">domain model</a> (entities implemented as classes)</p></li><li><p>using an ORM that maps those entities to database artifacts (mostly tables but not limited to),</p></li><li><p>through <a href="https://martinfowler.com/eaaCatalog/repository.html">repositories</a> that serve those entities to the rest of the system, from controllers to domain or application services,</p></li><li><p>leveraging Entity Framework (so we&#8217;re talking about a <code>C#/.NET</code> application).</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1mVE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1mVE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1mVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg" width="1456" height="708" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2662475,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.well-tempered.net/i/188551629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1mVE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1mVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2318d0c0-6165-4ad5-8c58-8ce6fe5e9efd_4032x1960.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once Entity Framework is in the picture, you quickly run into the question of how to manage the lifetime of <code>DbCo</code>ntext: specifically, how to share it across multiple repositories participating in what we consider &#8220;one&#8221; business transaction.</p><p>After trying a few approaches, I ended up adopting the pattern described in <a href="https://mehdi.me/ambient-dbcontext-in-ef6/">Mehdi El Gueddari&#8217;s brilliant piece</a> about ambient <code>DbContext</code> scope (worth reading before continuing).</p><p>Let us present the same example as that post does: a <code>UserService</code> with a <code>MarkUserAsPremium</code> method (I won&#8217;t repeat the repository implementation here &#8212; it&#8217;s not the interesting part).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;28fdc493-b681-4b37-a56b-e6391453c860&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">class UserService : IUserService
{
    private IUserRepository _userRepository;
    private IDbContextScopeFactory _dbContextScopeFactory;

    public void MarkUserAsPremium(Guid userId)
    {
        using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create())
        {
            User user = _userRepository.Get(userId);
            user.IsPremiumUser = true;
            dbContextScope.SaveChanges();
        }
    }
}</code></pre></div><p>The key takeaway is this: the <code>IDbContextScope / IDbContextScopeFactory</code> pair is, in practice, an implementation detail. It ultimately exists to support repository behavior across different actor boundaries, and repository code may rely on the presence of an ambient scope. But the service itself claims to deal in abstractions: entities, repositories, business operations. Basically, in a sense we are breaking the fourth wall <em>and the abstractions along with it</em>.</p><p>To restore coherence we must ask ourselves what does <code>IDbContextScope</code> really mean? To answer this we must first ask ourselves what does it do? Well:</p><ul><li><p>it defines the boundary of an abstract business transaction</p></li><li><p>and the boundary of the corresponding unit of work against the database.</p></li></ul><p>Once you see it that way, the next step becomes obvious: name the thing after what it means, not after how it&#8217;s implemented. Make the intent explicit, without forcing the rest of the code base to learn about a relatively minor implementation artifact.</p><p>Now we can introduce two interfaces that serve as a mild abstraction that keeps things within the ball park of our domain-related semantics:</p><ul><li><p><code>IBusinessTransaction</code> to <em>explicitly</em> convey the meaning currently carried by <code>IDbContextScope</code>;</p></li><li><p>and <code>IBusinessTransactionCoordinator</code> to explicitly convey the meaning currently carried by <code>IDbContextScopeFactory</code>.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;c7139c91-0435-4351-a22c-c7c9d8e42166&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public interface IBusinessTransactionCoordinator
{
    IBusinessTransaction BeginTransaction();
}

public interface IBusinessTransaction : IDisposable
{
    void Commit();
    Task CommitAsync();
    Task CommitAsync(CancellationToken cancelToken);

    void Rollback();
    Task RollbackAsync();
}</code></pre></div><p>As you can imagine, the implementation is intentionally straightforward. First, <code>IBusinessTransaction</code> is merely a fa&#231;ade over <code>IDbContextScope</code>. It doesn&#8217;t create the scope; it merely ensures correct manipulation.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;0866121d-dc09-424c-b110-1641aafd38b1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class DbContextScopeBusinessTransaction : IBusinessTransaction
{
&#9;private bool mIsDisposed = false;

&#9;private IDbContextScope mDbContextScope;

&#9;public DbContextScopeBusinessTransaction( IDbContextScope dbContextScope )
&#9;{
&#9;&#9;mDbContextScope = dbContextScope
&#9;&#9;&#9;?? throw new ArgumentNullException( nameof( dbContextScope ) );
&#9;}

&#9;private void EnsureNotDisposedOrThrow()
&#9;{
&#9;&#9;if (mIsDisposed)
&#9;&#9;&#9;throw new ObjectDisposedException( nameof( DbContextScopeBusinessTransaction ) );
&#9;}

&#9;public void Commit()
&#9;{
&#9;&#9;EnsureNotDisposedOrThrow();
&#9;&#9;mDbContextScope.SaveChanges();
&#9;}

&#9;public async Task CommitAsync()
&#9;{
&#9;&#9;EnsureNotDisposedOrThrow();
&#9;&#9;await mDbContextScope.SaveChangesAsync().ConfigureAwait( false );
&#9;}

&#9;public async Task CommitAsync( CancellationToken cancelToken )
&#9;{
&#9;&#9;EnsureNotDisposedOrThrow();
&#9;&#9;await mDbContextScope.SaveChangesAsync( cancelToken ).ConfigureAwait( false );
&#9;}

&#9;public void Rollback()
&#9;{
&#9;&#9;EnsureNotDisposedOrThrow();
&#9;&#9;Dispose();
&#9;}

&#9;public Task RollbackAsync()
&#9;{
&#9;&#9;EnsureNotDisposedOrThrow();
&#9;&#9;Dispose();
&#9;&#9;return Task.CompletedTask;
&#9;}

&#9;protected void Dispose( bool disposing )
&#9;{
&#9;&#9;if (!mIsDisposed)
&#9;&#9;{
&#9;&#9;&#9;if (disposing)
&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;mDbContextScope.Dispose();
&#9;&#9;&#9;&#9;mDbContextScope = null;
&#9;&#9;&#9;}

&#9;&#9;&#9;mIsDisposed = true;
&#9;&#9;}
&#9;}

&#9;public void Dispose()
&#9;{
&#9;&#9;Dispose( true );
&#9;&#9;GC.SuppressFinalize( this );
&#9;}
}</code></pre></div><p>In this implementation, although no explicit rollback operation is provided by the underlying <code>IDbContextScope</code>, we simply dispose the scope without calling <code>Commit()</code>, which mirrors the behavior of <code>DbTransaction</code> in <code>ADO.NET</code>.</p><p>Second, <code>IBusinessTransactionCoordinator</code> simply creates that fa&#231;ade, using an <code>IDbContextScopeFactory</code> to manufacture the underlying scope. It could take on additional responsibilities later, but for our purposes this is enough:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;1a917a6c-1267-4853-8639-58a3fa9e2440&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class DbContextScopeBusinessTransactionCoordinator : IBusinessTransactionCoordinator
{
&#9;private IDbContextScopeFactory mDbContextScopeFactory;

&#9;public DbContextScopeBusinessTransactionCoordinator( IDbContextScopeFactory dbContextScopeFactory )
&#9;{
&#9;&#9;mDbContextScopeFactory = dbContextScopeFactory
&#9;&#9;&#9;?? throw new ArgumentNullException( nameof( dbContextScopeFactory ) );
&#9;}

&#9;public IBusinessTransaction BeginTransaction()
&#9;{
&#9;&#9;IDbContextScope dbContextScope = mDbContextScopeFactory.Create();
&#9;&#9;return new DbContextScopeBusinessTransaction( dbContextScope );
&#9;}
}</code></pre></div><p>Their usage stays the same as the artifacts they abstract away. It even becomes simpler to read because the code now says what it means. And it allows us to avoid referencing that assembly in our domain code base.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;79feedda-7a73-4b88-8fac-bfb36be1252d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class UserService : IUserService
{
    private readonly IUserRepository _userRepository;

    private readonly IBusinessTransactionCoordinator _txCoordinator;

    //...

    public void MarkUserAsPremium(Guid userId)
    {
        using (IBusinessTransaction tx = _txCoordinator.BeginTransaction())
        {
            User user = _userRepository.Get(userId);
            user.IsPremiumUser = true;
            tx.Commit();
        }
    }
}</code></pre></div><p>Nothing magical happened. The data access pattern is the same. The behavior is the same. What changed is the story the code tells: the service executes a business transaction and commits it. The infrastructure may still rely on ambient <code>DbContext </code>scope, but the service (or nobody else for that matter) doesn&#8217;t need to know or care.</p><p>These are also small enough to carry them around in your back pocket. For instance, what I usually do is:</p><ul><li><p>create a <code>MyProject.ModelAPI</code> project into which I implement common primitives in support of domain-related entities and other actors such as domain services and for me it&#8217;s the ideal place to host these two interface;</p></li><li><p>create a <code>MyProject.ModelInfrastructure</code> project into which I usually place everything related to common domain infrastructure concerns, including the interface implementations laid out here.</p></li></ul><p>As you can imagine, this approach allows for easily mocking out the transaction behavior, as well as swapping the unit of work management later on, thus ensuring that the cognitive cost of an additional abstraction is justified by the clarity it brings.</p><p>Naming an infrastructure concern after what it truly represents may seem like a small adjustment. And it <em>is</em>. But small semantic shifts <em>accumulate</em>.</p><p>Once you start treating boundaries seriously &#8212; transactions, units of work, domain concepts &#8212; you inevitably begin asking a larger question: how much of our code is describing intent, and how much is merely plumbing?</p>]]></content:encoded></item></channel></rss>