<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Ian Wold</title>
    <link>https://ian.wold.guru/</link>
    <description>Ian Wold's Blog</description>
    <a10:link rel="self" href="https://ian.wold.guru/feed.xml" />
    <item>
      <guid isPermaLink="false">90_of_my_homepage_was_useless</guid>
      <link>https://ian.wold.guru/Posts/90_of_my_homepage_was_useless.html</link>
      <title>90% of my Homepage was Useless</title>
      <description>&lt;p&gt;One week ago, my homepage was around 550k uncompressed and took about as many ms to load. I thought this was light, though I'd have liked it to be faster - I just attributed this to a side-effect of hosting with GitHub pages.&lt;/p&gt;
&lt;p&gt;This weekend I discovered the &lt;a href="https://512kb.club"&gt;512kb Club&lt;/a&gt; which ranks websites by how big they are, the catch being that you need to be below 512k uncompressed. I looked at some of the sites on the high end of that list, and I felt pretty disappointed. There were sites that had much more content, some with many large images, while staying below 512k. I have an SVG and a bunch of black-and-white text.&lt;/p&gt;
&lt;p&gt;Well, I made some small optimizations and got it below the threshold, but then I caught the bug and &lt;em&gt;presto changeo&lt;/em&gt; before I knew it I had eliminated 90% of my website &lt;em&gt;and it is pixel-perfect to how it was before&lt;/em&gt;. I'm now at 62.8k, a member of the &amp;quot;green team&amp;quot; on the 512kb Club site, and equally important I reduced the load time of the site (by any time metric you like) by around 50% just with the size optimizations.&lt;/p&gt;
&lt;h1&gt;Check Your Fonts&lt;/h1&gt;
&lt;p&gt;The single most impactful thing I did was I checked my fonts. I use Google WebFonts, and their API gives you a few opportunities for improvement. First, make sure you're not loading any fonts you're not using.&lt;/p&gt;
&lt;p&gt;Then eliminate any weights you're not using. I use &lt;code&gt;Rubik&lt;/code&gt; for my text, and the homepage only uses weights 300 and 500, but I had been loading everything from 100 to 900:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;link href=&amp;quot;https://fonts.googleapis.com/css2?family=Rubik:wght@100;200;300;400;500;600;700;800;900&amp;amp;display=swap&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&amp;lt;link href=&amp;quot;https://fonts.googleapis.com/css2?family=Rubik:wght@300;500&amp;amp;display=swap&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use &lt;code&gt;Montserrat&lt;/code&gt; for my name at the top and nothing else, and Google provides an API for you to specify the exact text you need to load in a font. This took &lt;code&gt;Montserrat&lt;/code&gt; from 30k to 2k or so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;link href=&amp;quot;https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900&amp;amp;display=swap&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&amp;lt;link href=&amp;quot;https://fonts.googleapis.com/css2?family=Montserrat:wght@700&amp;amp;display=swap&amp;amp;text=IAN%20WOLD&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Optimize Your SVGs&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://svgomg.net/"&gt;SVGOMG&lt;/a&gt; is a great tool for this. The SVG at the top of my page was 64k, now it's about 8k. &lt;em&gt;That's huge!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I had also previously been using &lt;a href="https://feathericons.com/"&gt;Feather Icon&lt;/a&gt;'s JS client to swap out &lt;code&gt;i&lt;/code&gt; tags for their icon SVGs. However, by directly embedding the SVGs (which is easy and maintainable because they've got a great site) I was able to eliminate 30k and several ms from my page load time.&lt;/p&gt;
&lt;h1&gt;Torchlight is Pretty Cool&lt;/h1&gt;
&lt;p&gt;I had been using &lt;a href="https://highlightjs.org/"&gt;Highlight.js&lt;/a&gt; for syntax highlighting, and it was easy enough to set up (I &lt;em&gt;highly&lt;/em&gt; recommend it if you need to get up and going fast) but I wasn't too much a fan of the highlighting for my cases, &lt;em&gt;and&lt;/em&gt; it was putting a 30k dependency on my site. Yes, I was able to take it off my homepage - no code there - but I figured I could also help my post pages out.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://torchlight.dev"&gt;Torchlight&lt;/a&gt; is absolutely awesome. They run a server with the same language servers that VSCode uses with an API that will perform syntax highlighting for your snippets. Best part is they've got a Node package that you can run on a local directory to detect any &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/code&gt; blocks in your HTML, post the code to their server, and swap out your code blocks with their highlighted blocks. It's free for non-revenue-generating sites, works like charm in the build step, and generates the best syntax highlighting with the most features.&lt;/p&gt;
&lt;h1&gt;Maybe Minify Your Pages&lt;/h1&gt;
&lt;p&gt;I only saved a few kb doing this. What's most important I think is to consider bundling assets that can be bundled to save on the chore of loading them from a server. If your files are sufficiently small (as they tend to be on a statically-generated site) you're not going to save too much by minifying. However, if it's easy to add, then go for it.&lt;/p&gt;
&lt;h1&gt;Next Optimizations&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;SEO&lt;/li&gt;
&lt;li&gt;Accessibility&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;The SVG at the top of the page still doesn't turn white in dark mode&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 06 Dec 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-12-06T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">adding_a_database_to_our_railway_app</guid>
      <link>https://ian.wold.guru/Posts/adding_a_database_to_our_railway_app.html</link>
      <title>Adding a Database to our Railway App</title>
      <description>&lt;p&gt;&lt;a href="https://ian.wold.guru/Posts/deploying_aspdotnet_7_projects_with_railway.html"&gt;Last time I looked at Railway&lt;/a&gt;, I got it up and running with a Blazor WASM app. Now, I'll look at adding a PostgreSQL database to it. As with the app I got working, Railway's interface will make it incredibly simple to provision the database, and we'll need to do minimal work to get the connection info to our app.&lt;/p&gt;
&lt;h1&gt;Provisioning the Database&lt;/h1&gt;
&lt;p&gt;From the Railway dashboard, you can click into your project, and there should be a New button at the top (as in a buttno that says &amp;quot;New&amp;quot;, not a button whose appearance would not heretofore be expected):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/railway-database-new.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;This brings up the familiar dialog to provision resources, from which we'll select Database:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/railway-database-new-project.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;And then I'm going to choose PostgreSQL here. Note that if Railway doesn't have an option here for the database you want, you can always create a plain Docker image from an image of your preferred database.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/railway-database-new-database.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;And look at that, it deploys a new PostgreSQL database! This is one of the things I love about Railway - their interface to set up resources has eliminated &lt;em&gt;all&lt;/em&gt; of the steps to setup that aren't 100% necessary, and the default settings they choose are logical and easily overwritten later if we need.&lt;/p&gt;
&lt;p&gt;Notice also the attached &lt;code&gt;pgdata&lt;/code&gt; volume. As discussed before, Railway just stores all of your resources in Docker images. This doesn't require that you create a &lt;code&gt;dockerfile&lt;/code&gt; for each of your projects and resources, they can do that for you if you don't want to. It is to say that they give you all the tools you might want to use to be able to manage your resources as docker containers and volumes, so you can more or less choose the level of control you need over your resources.&lt;/p&gt;
&lt;p&gt;The way I use Railway, I let it manage all my resources for me, and I don't bog myself down in the weeds of Docker as much. I've got plenty of time in my day job to get frustrated with Docker!&lt;/p&gt;
&lt;h1&gt;Connecting to our Database&lt;/h1&gt;
&lt;p&gt;If you click on your new database resource and then the &lt;code&gt;Variables&lt;/code&gt; tab, you should see a bunch of variables:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/railway-database-variables.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;This contains all the information that our app will need to connect to it. Railway allows the resources within a project to reference each others' variables, so we just need to know which ones to reference to build a connection string.&lt;/p&gt;
&lt;p&gt;Now, they've all got incredibly obvious names so you can certainly accomplish this by guessing in as much time as it takes you to read this article, but for thoroughness' sake I've got a connection string here:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;User Id=${{Postgres.PGUSER}};Password=${{Postgres.PGPASSWORD}};Host=${{Postgres.PGHOST}};Port=${{Postgres.PGPORT}};Database=${{Postgres.PGDATABASE}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you copy that as the value of a new variable in your application, it will fill in each of the references with the values from your database's variables, and that should be a sufficient connectino string to test that it works:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/railway-database-connection-string.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Note that I'm using the variable name &lt;code&gt;ConnectionStrings__Database&lt;/code&gt;, with two underscores, which behaves as though I'm inserting the following into my &lt;code&gt;appsettings&lt;/code&gt; (in a .NET context):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;&amp;quot;ConnectionStrings&amp;quot;: {
    &amp;quot;Database&amp;quot;: &amp;quot;...&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's all the configuration needed for your app to be able to consume your new database!&lt;/p&gt;
</description>
      <pubDate>Wed, 08 Nov 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-11-08T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">a_scrum_odyssey</guid>
      <link>https://ian.wold.guru/Posts/a_scrum_odyssey.html</link>
      <title>A Scrum Odyssey</title>
      <description>&lt;p&gt;I had an unexpected and not entirely unpleasant experience in a recent retrospective meeting: all of the engineers on the team moved to stop having daily standup meetings altogether. Although I'm no stranger to working without standup meetings, and in many ways I prefer that way of working, I don't think it's unreasonable to suggest that the majority of engineers are fine with well-structured daily standups.&lt;/p&gt;
&lt;p&gt;Even with a team that enjoys standup meetings however, there's value in breaking the norm, becoming more flexible, and trying things out. Maybe such a team would settle back into daily standups, maybe they'd find something else that works for them. Teams which don't enjoy their standups though will almost certaily settle on a new scheme.&lt;/p&gt;
&lt;p&gt;I sat down to type out a guide on how and why a team might experiment with alternative approaches to the daily standup. But that came out so incredibly dry I weaved those points into a story demonstrating a hypothetical team undergoing that process. That resulted in something which I'm not entirely sure got the point across. But then I remembered that ChatGPT (insert eye roll here) is actually pretty good at generating Shakespearean sonnets (insert double eye roll here). So, I ran my story through the ringer and I present to you &lt;strong&gt;A Scrum Odyssey: A journey away from daily scrum meetings, as a cycle of eight Shakespearean sonnets&lt;/strong&gt;:&lt;/p&gt;
&lt;h1&gt;The Rite of Dawn's Assembly&lt;/h1&gt;
&lt;p&gt;In days of yore, where standups held their sway,&lt;br /&gt;
Religious teams to this rite did cling tight.&lt;br /&gt;
Believing in its might, they'd all display&lt;br /&gt;
Their tasks and toils, at morning's first light.&lt;/p&gt;
&lt;p&gt;“'Tis the only path!” they’d loudly decree,&lt;br /&gt;
To boost our voice and our work's potency.&lt;br /&gt;
Yet, in this sea of daily scrutiny,&lt;br /&gt;
Did they e'er question its necessity?&lt;/p&gt;
&lt;p&gt;For in their hearts, joy and productivity,&lt;br /&gt;
Seemed hand in hand with scrum's consistency.&lt;br /&gt;
But having not trod another pathway,&lt;br /&gt;
How sure were they, in their daily ballet?&lt;/p&gt;
&lt;p&gt;To question not is to be but a drone,&lt;br /&gt;
Yet change may show more ways than they've known.&lt;/p&gt;
&lt;h1&gt;Whispers 'Midst the Court of Teams&lt;/h1&gt;
&lt;p&gt;As time doth pass, objections loud do cry,&lt;br /&gt;
Standups draw long, and patience wears so thin.&lt;br /&gt;
Some feel the gaze of oversight too nigh,&lt;br /&gt;
Whilst others sigh at tales they're lost within.&lt;/p&gt;
&lt;p&gt;The daily rite, once held in high esteem,&lt;br /&gt;
Now burdens hearts, and feels not quite so lean.&lt;br /&gt;
&amp;quot;Maintain the stand!&amp;quot; the managers exclaim,&lt;br /&gt;
&amp;quot;For knowledge's spread, 'tis the only regime.&amp;quot;&lt;/p&gt;
&lt;p&gt;Another quips, &amp;quot;'Tis how we stay aligned!&amp;quot;&lt;br /&gt;
Yet in their hearts, a restlessness they find.&lt;br /&gt;
The status quo, now feels a heavy chain,&lt;br /&gt;
With every voice, there grows a silent pain.&lt;/p&gt;
&lt;p&gt;Unease doth spread, as waters dark and deep,&lt;br /&gt;
The team in hope, for change they might yet reap.&lt;/p&gt;
&lt;h1&gt;Into Uncharted Councils Steered&lt;/h1&gt;
&lt;p&gt;In growing dissent, the teams make their stand,&lt;br /&gt;
To question the rites of daily discourse.&lt;br /&gt;
With keen eyes they search, across the vast land,&lt;br /&gt;
For new methods, their spirit to endorse.&lt;/p&gt;
&lt;p&gt;The captains do doubt, and their concerns voice,&lt;br /&gt;
Yet vibrant ideas do from teams arise.&lt;br /&gt;
&amp;quot;Demos!&amp;quot; cries one, &amp;quot;Let our works make their noise!&amp;quot;,&lt;br /&gt;
Another suggests, one-on-one 'neath the skies.&lt;/p&gt;
&lt;p&gt;Pair programming, reviews, new tactics bloom,&lt;br /&gt;
As weary traditions begin to wane.&lt;br /&gt;
The tides of change, they sweep away the gloom,&lt;br /&gt;
Old ways dissolve, as new paths become plain.&lt;/p&gt;
&lt;p&gt;Thus in the dance of time and adaptability,&lt;br /&gt;
Teams find their stride, and new possibility.&lt;/p&gt;
&lt;h1&gt;Quests in the Halls of Converse&lt;/h1&gt;
&lt;p&gt;In search of ways, the teams diverge their path,&lt;br /&gt;
Asynchronous rites, emails that they pen,&lt;br /&gt;
Fewer meets they seek, to avoid the wrath,&lt;br /&gt;
Of daily drudgery, again and again.&lt;/p&gt;
&lt;p&gt;More intimate chats, one-on-one they hold,&lt;br /&gt;
In pair programming, they sharpen their skill.&lt;br /&gt;
Exploring each method, both new and old,&lt;br /&gt;
They weigh every boon, every bitter pill.&lt;/p&gt;
&lt;p&gt;Managers, once stern, now their gaze doth shift,&lt;br /&gt;
Witnessing changes, benefits unfold.&lt;br /&gt;
Yielding their ground, as sands of time do sift,&lt;br /&gt;
Embracing the new, releasing the old.&lt;/p&gt;
&lt;p&gt;In the grand ballet of work's ebb and flow,&lt;br /&gt;
Teams evolve, and brighter futures they sow.&lt;/p&gt;
&lt;h1&gt;Where Two Worlds in Concert Meet&lt;/h1&gt;
&lt;p&gt;From all the trials, some choices do emerge,&lt;br /&gt;
Asynchronous standups, the globe's teams cheer.&lt;br /&gt;
For visual minds, digital boards surge,&lt;br /&gt;
As preferred tools, their thoughts to make clear.&lt;/p&gt;
&lt;p&gt;Meetings grow few, much to all's delight,&lt;br /&gt;
Those that remain, are purposeful and keen.&lt;br /&gt;
Gone are the days of endless oversight,&lt;br /&gt;
In place, moments of value are seen.&lt;/p&gt;
&lt;p&gt;&amp;quot;Support!&amp;quot; cry the captains, in newfound glee,&lt;br /&gt;
Feeling the strength of their teams' tethered core.&lt;br /&gt;
From the throes of old, they now are set free,&lt;br /&gt;
Bonded and strong, as never before.&lt;/p&gt;
&lt;p&gt;From trials and tests, the best paths are clear,&lt;br /&gt;
Teams and their leaders, in harmony steer.&lt;/p&gt;
&lt;h1&gt;Musing on Fortune's Fickle Hand&lt;/h1&gt;
&lt;p&gt;With shifts in tide, come trials unforeseen,&lt;br /&gt;
Some souls do long for morning meetings past.&lt;br /&gt;
An anchor lost, in routines once serene,&lt;br /&gt;
Others claim that commitment's not so vast.&lt;/p&gt;
&lt;p&gt;Managers note, their gaze oft on boards dwell,&lt;br /&gt;
More than before, in this changed paradigm.&lt;br /&gt;
For in each change, some benefits dispel,&lt;br /&gt;
Replaced by costs, in this evolving time.&lt;/p&gt;
&lt;p&gt;&amp;quot;No method’s perfect,&amp;quot; the team concedes true,&lt;br /&gt;
Every path has its toll, its weight to bear.&lt;br /&gt;
But in pursuit of what’s best, they pursue,&lt;br /&gt;
Balancing gains, with costs they must wear.&lt;/p&gt;
&lt;p&gt;In life’s grand dance of loss and of gain,&lt;br /&gt;
Teams learn to thrive, through pleasure and pain.&lt;/p&gt;
&lt;h1&gt;Melding Yesteryears with the Morrow&lt;/h1&gt;
&lt;p&gt;From experiments vast, wisdom teams glean,&lt;br /&gt;
Blending the best from methods they've tried.&lt;br /&gt;
A hybrid emerges, both fresh and seen,&lt;br /&gt;
Balancing old and new, side by side.&lt;/p&gt;
&lt;p&gt;Asynchronous notes, for days in-between,&lt;br /&gt;
With face-to-face meets, weekly they decide.&lt;br /&gt;
Though returning to paths once before seen,&lt;br /&gt;
They come renewed, with broader stride.&lt;/p&gt;
&lt;p&gt;Most vital of all, in this new phase,&lt;br /&gt;
Teams claim their process, its reins they hold.&lt;br /&gt;
Each member engaged, in collective praise,&lt;br /&gt;
For a system by them, shaped and molded.&lt;/p&gt;
&lt;p&gt;From trials diverse, a way they have found,&lt;br /&gt;
Where every voice in harmony does sound.&lt;/p&gt;
&lt;h1&gt;Ascendance Beyond Time's Norms&lt;/h1&gt;
&lt;p&gt;In newfound grace, the teams now flex and bend,&lt;br /&gt;
Having learnt the worth of norms held askew.&lt;br /&gt;
Witnessed have they, to what change can tend,&lt;br /&gt;
The boon of steps, both retraced and anew.&lt;/p&gt;
&lt;p&gt;The culture's hue, to feedback does incline,&lt;br /&gt;
Embracing shifts, with open arms and heart.&lt;br /&gt;
Where once was rigidity, now they entwine&lt;br /&gt;
Flexibility, as their foremost art.&lt;/p&gt;
&lt;p&gt;Managers see, that control's grip too tight,&lt;br /&gt;
Does not always lead to the best of ways.&lt;br /&gt;
Productivity, in this newfound light,&lt;br /&gt;
Reaches peaks unseen, to everyone's praise.&lt;/p&gt;
&lt;p&gt;From challenging norms, to heights unforeseen,&lt;br /&gt;
They've journeyed forth, with purpose evergreen.&lt;/p&gt;
</description>
      <pubDate>Fri, 20 Oct 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-10-20T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">bandwidth_is_infinite_ly_troublesome</guid>
      <link>https://ian.wold.guru/Posts/bandwidth_is_infinite_ly_troublesome.html</link>
      <title>Bandwidth is Infinite ... ly Troublesome</title>
      <description>&lt;p&gt;In my last &lt;a href="https://ian.wold.guru/Posts/lateny_is_zero.html"&gt;post on latency&lt;/a&gt; I cited an interesting definition given by David Boike: &lt;code&gt;TotalTime = Latency + (Size / Bandwidth)&lt;/code&gt;. This suggests that latency and bandwidth are related, and that's intuitively true. At a very simplified level, one deals with packet count and the other with packet size; these add together into a concept of packet volume, and so issues related to each do compound on each other. In that article, I was able to demonstrate some techniques which, depending on how aggressive you might be able to be with your system's requirements - can very largely mitigate issues related to latency.&lt;/p&gt;
&lt;p&gt;Bandwidth is not, I think, so simple. Indeed, the total bandwidth of the world has increased markedly year over year, but then our demand for it has as well. In some cases our demand is outpacing our ability to increase bandwidth. Theoretically we &lt;em&gt;could&lt;/em&gt; increase bandwidth enough to meet way more than we could ever demand, but a 1-mile-diameter cable between New York and London sounds slightly infeasible. I'm not a cable engineer so I won't get too deep into that debate, but until they give us that cable we'll need to deal with bandwidth.&lt;/p&gt;
&lt;p&gt;This is the third fallacy of distributed computing, that bandwidth is infinite. Of course there's a physical limit to how much data we can send, and there's not really any clever tricks here that can mitigate that terribly well. The first step, maybe the most difficult, would be to not transmit erroneous data. From there, we can compress data, tier it by importance, and maybe shift around where it physically gets moved. Nonetheless, we can't adopt an attitude which ignores it.&lt;/p&gt;
&lt;h1&gt;Bandwidth Happens&lt;/h1&gt;
&lt;p&gt;I'll end up sounding quite like a broken record in this article, but it's for good cause. If you're sending a message over the wire, you're consuming bandwidth. The best strategy to prevent this from becoming an issue is to not send data over the wire. Don't architect a distributed system. That's not entirely satisfactory - some systems do need to be distributed. Some systems &lt;em&gt;have&lt;/em&gt; to serve their users over a website.&lt;/p&gt;
&lt;h2&gt;A Cost of Doing Business&lt;/h2&gt;
&lt;p&gt;It's apt in a couple ways to suggest that bandwidth is a cost of doing business. Of course it costs money to consume; distributed systems need to pay for this, and large systems are going to have to pay more. Cloud providers know this, and depending on your case you might face an &lt;a href="https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0"&gt;eye-watering bill&lt;/a&gt;. Each unit of bandwidth can be exponentially more expensive at higher levels, and this is a more immediate limitation on our systems than that absolute bandwidth is capped.&lt;/p&gt;
&lt;p&gt;Describing it as a cost of doing business is apt also because any distributed system requires &lt;em&gt;some&lt;/em&gt; consumption of bandwidth; this is a cost in itself. While we have some control over whether messages in our system incur latency, we have no control over that they use up bandwidth. This potentially makes bandwidth-related problems more severe; there are fewer tools at my disposal to mitigate these problems.&lt;/p&gt;
&lt;h2&gt;Induced Demand&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Induced_demand"&gt;Induced demand&lt;/a&gt; is a term used in urban planning, it's a phenomenon whereby increasing the number of lanes in a highway causes traffic problems to worsen as it causes more people to choose the highway to commute, thinking that the added capacity will benefit their travel. I think the same thing happens to us with bandwidth. As our bandwidth capacity has increased, users expect to be able to consume more data, businesses expect to be able to ingest and work with more data, and as engineers we architect and build systems which fulfil these requests. We see they're constantly adding more cables and we see other systems working with much more data, we feel more and more comfortable transporting more of our own data.&lt;/p&gt;
&lt;p&gt;Media and streaming has become more ubiquitous and has come to incur a higher cost. Ten years ago I was happy with 720p video from YouTube, now I can stream 4k! I stream 4k video from Netflix and haven't touched a DVD in years, I stream music all day instead of listening through my CD catalog. Videos play instantly on my social media apps now.&lt;/p&gt;
&lt;p&gt;IoT has caused a huge proliferation of devices connected to the internet, and if these devices aren't streaming data somewhere they're phoning home with some frequency. Cloud computing has made it cheaper and easier than ever to set up systems that add to the problem, and lately even some small sized companies are interested in what they can do with big data.&lt;/p&gt;
&lt;p&gt;How much of the data we're sending over the wire is erroneous? I don't know the exact number, but I know it's not 0% - I've written software that's piped unnecessary data between services! I apologize for my part in this. For what it's worth, I do try - more now than earlier in my career - to keep the &lt;em&gt;amount&lt;/em&gt; of data to a minimum. We should all be proud to build a system that is as light on the internet infrastructure - and our private infrastructure - as possible.&lt;/p&gt;
&lt;h1&gt;Mitigating Patterns&lt;/h1&gt;
&lt;p&gt;Once we've eliminated erroneous data from being transmitted around our system, we can take some steps to reduce the strain we put on the cables. The smaller we can make our messages the better, and we have a couple of patterns and practices to follow here.&lt;/p&gt;
&lt;p&gt;If we've reduced our packets as much as we can, then we need to deal with the physicality of our traffic. Monitoring and prioritizing critical traffic or geolocating where the bulk of that traffic happens can give us more fine-grained control over keeping our bandwidth usage in check.&lt;/p&gt;
&lt;p&gt;And if &lt;em&gt;those&lt;/em&gt; strategies fail, we can always throw our hands up, despair, and yell at the cloud. Haha, get it? The &lt;em&gt;cloud&lt;/em&gt;!&lt;/p&gt;
&lt;h2&gt;Serialization and Compression&lt;/h2&gt;
&lt;p&gt;The first and most obvious tool to reduce the amount of data sent over the wire is ... reducing the amount of data sent over the wire. Maybe that seems obvious but a lot of folks miss this first step, especially serialization!&lt;/p&gt;
&lt;p&gt;JSON has become the ubiquitous way to serialize data, but it has plenty of inefficiencies, other formats use fewer characters to represent the structure of the data and can be parsed much faster. Indeed, I would expect that there's a correlation between how many characters a format requires to represent a structure and the speed of (de)serialization.&lt;/p&gt;
&lt;p&gt;For those of us in the .NET world, &lt;a href="https://github.com/Cysharp/MemoryPack"&gt;MemoryPack&lt;/a&gt; is an attractive option, being up to 200 times faster than the first-party &lt;code&gt;System.Text.Json&lt;/code&gt; and coming in at a smaller serialized output than JSON. The catch of course that prevents this from being used in a lot of distributed applications is that the client needs to be able to deserialize it - you'll need to either use .NET or their TypeScript code generator. At least if I use JSON then anyone will be able to deserialize it.&lt;/p&gt;
&lt;p&gt;An attractive option then is &lt;a href="https://protobuf.dev/"&gt;protobuf&lt;/a&gt;, a fast and small format developed by Google. This format is relatively ubiquitous and has demonstrated real-world effectiveness: LinkedIn adopted it and &lt;a href="https://www.infoq.com/news/2023/07/linkedin-protocol-buffers-restli/"&gt;reduced latency by 60%&lt;/a&gt;! Though they measured latency, these gains were due to smaller message sizes resulting in a reduced impact on bandwidth.&lt;/p&gt;
&lt;p&gt;Compression is the other factor here, and it's a saving grace that there are a fair number of compression algorithms available to us; we need to worry less about language-specific tools when we consider compression. Algorithms like gzip and brotli have many implementations in every language, and MemoryPack even comes with a brotli compressor built in. However, speed is crucial here. Plenty of compression algorithms are fast enough but this is important to consider.&lt;/p&gt;
&lt;h2&gt;Claim Check&lt;/h2&gt;
&lt;p&gt;Simply put, this pattern suggests using a URL to the location of a resource rather than the resource itself as the payload across the wire. You've surely encountered systems where images or other files are stored in a CDN separate to the application's persistence, and URLs are used to represent these resources. This is sort of the same idea.&lt;/p&gt;
&lt;p&gt;This is a good pattern to use with asynchronous communication if events contain enough data - you can store the payload in a persistence system and just pass the URL as the event content. Naturally though, you might need to include some subsection of the data with the URL so that consumers can identify the data and choose whether and how to react; you wouldn't be saving much bandwidth if each consumer needed to fetch the data at the URL to check it.&lt;/p&gt;
&lt;p&gt;Naturally, this is much more effective for larger payloads, and might be harmful for very small amounts of data. I'd expect though that you're not running into bandwidth issues with small events though. I would also suspect that geography plays a part in deciding at which size of resource this pattern might be a benefit; penalties for long-distance data transfer are not incurred solely due to bandwidth limitations!&lt;/p&gt;
&lt;p&gt;As an aside, I will say that I've never actually seen this pattern used except insofar as keeping images on a separate CDN could be considered related. However, I was speaking with a colleague the other day, inquiring about his firm's approach to mitigating bandwidth concerns, and he said something to the effect of &amp;quot;If we have bandwidth issues we just 'S3' it.&amp;quot; I suppose this pattern is quite natural then!&lt;/p&gt;
&lt;h2&gt;Traffic Management&lt;/h2&gt;
&lt;p&gt;In a lot of cases, bandwidth becomes an issue when it affects a particular type of resource, especially content that we need to deliver to a user or data that's otherwise in the &amp;quot;hot path&amp;quot;. It makes sense that &lt;em&gt;if&lt;/em&gt; we're going to have issues with bandwidth then we're going to at least want to be able to keep those issues away from certain paths.&lt;/p&gt;
&lt;p&gt;Managing traffic to shape bandwidth concerns can be as simple as setting bandwidth or rate limits on different parts of your network. By throttling non-critical systems you're naturally freeing up resources to be able to be consumed by other paths. This can require some monitoring and fine-tuning, but it's a simple solution that can be effective.&lt;/p&gt;
&lt;p&gt;Depending on your use case, you might either want to buffer or drop packets when bandwidth limits are hit. I think the default option would be buffering for obvious reasons - we generally want to preserve data. Dropping packets is generally preferable though in scenarios like streaming audio or video to a client (where buffering can compound on itself) or for non-critical data like logs or telemetry data, if the occasional loss doesn't have a significant impact. IoT comes to mind here - systems which stream large amounts of sensor data will probably benefit more from dropping packets.&lt;/p&gt;
&lt;p&gt;Setting up a priority queue is only slightly more involved but a more elegant and controllable solution. Message queues typically support this feature, as do gateways like firewalls and load balancers. From an architecture perspective, it's probably good practice anyway to intentionally identify and appropriately segregate the critical parts of the system anyway.&lt;/p&gt;
&lt;h2&gt;Edge Computing&lt;/h2&gt;
&lt;p&gt;Redesigning your application to run at the edge is probably (read: certainly) overkill if you're &lt;em&gt;just&lt;/em&gt; trying to solve bandwidth issues, but it should be clear that edge computing gives you more control over how and where you send messages, which allows you to shape bandwidth use at different points in the network.&lt;/p&gt;
&lt;p&gt;By handling local traffic locally, you're not just keeping data away from your central system, you're also potentially shortening the wires over which the bulk of your data is transferred; bandwidth issues are more issuesome at larger distances. This is almost a requirement anymore for streaming applications - I can't imagine that Netflix could service most users streaming 4k data from New York to Hong Kong. The centralized systems typically get the lion's share of bandwidth allocation, but interestingly edge computing challenges this because the nodes of the network become bandwidth-heavy. Again, this is an advantage for systems which have bandwidth concerns that are heavily geographically-influenced.&lt;/p&gt;
&lt;p&gt;Edge computing adds a fair bit of complexity and a heap of cost though, and that's why I suggest that it's not a solution exclusively for solving bandwidth concerns. I think it's fair to be skeptical of edge computing except for cases that truly require it. If you are computing &lt;em&gt;on the edge&lt;/em&gt; though, you're probably dealing with a fair amount of data and one of the multitude of concerns which should motivate the architecture of your system should be these bandwidth considerations.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Our key first strategy to minimizing latency concerns is to send fewer requests, and our key first strategy to mitigating bandwidth concerns is to send less data. This seems like we're trapped then, like there's a hard, physical limit to the amount of data we can send at any one time. Indeed, this is the case. It seems like we're naturally pushed towards not distributing our system. Again, this is not just a reasonable conclusion but probably the correct one. Distributed systems are more complex, more error prone, and more costly. By being careful about &lt;em&gt;what&lt;/em&gt; we distribute we can manage that complexity when we have to.&lt;/p&gt;
&lt;p&gt;In those cases where distribution is required, approaching their design with all of these fallacies in mind will only get us so far too. Large streaming systems still fail with some frequency and can only fall back on managing their users' expectations with a thoughtful error message when there simply isn't enough bandwidth to deliver their video.&lt;/p&gt;
</description>
      <pubDate>Fri, 26 Apr 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-04-26T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">best_business_brunch_spots_in_minneapolis</guid>
      <link>https://ian.wold.guru/Posts/best_business_brunch_spots_in_minneapolis.html</link>
      <title>The Best Business Brunch in Minneapolis (So Far)</title>
      <description>&lt;p&gt;I've been working as a software engineer in the Twin Cities for quite some time, and I value keeping up with my colleagues here. The best way to meet up with folks is a well-timed Business Brunch™, and Minneapolis offers many great options for a successful business brunch. What I look for in a place is fast service, food that's good and familiar, an atmosphere that allows conversation, and well-priced mimosas (most importantly). Over the last year and a half I've been branching out and trying many places for business brunches, and I'm happy to share this list of the best spots &lt;em&gt;so far&lt;/em&gt;! If you're in Minneapolis you probably already know many of these spots, and I can recommend them for a business brunch. If you find yourself visiting and need to meet a colleague, then look no further:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thebreakfastclubmpls.com/"&gt;The Breakfast Club&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.briarbar.com/"&gt;The Briar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bryantlakebowl.com/"&gt;Bryant Lake Bowl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eggysdiner.com/"&gt;Eggy's Diner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hazelsnortheast.com/"&gt;Hazel's Northeast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.heathersmpls.com/"&gt;Heather's&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.henhouseeatery.com/"&gt;Hen House&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hi-lo-diner.com/"&gt;Hi-Lo Diner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maps.app.goo.gl/Rf2bCoqYsy1aLi5J6"&gt;Ideal Diner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariascafe.com/"&gt;Maria's Cafe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.our-kitchen.com/"&gt;Our Kitchen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tinydiner.com/"&gt;Tiny Diner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.victors1959cafe.com/"&gt;Victor's 1959 Cafe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 02 May 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-05-02T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club-2-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club-2-2024.html</link>
      <title>Book Club 2/2024: Recovering from TDD and Unit Tests</title>
      <description>&lt;p&gt;I'm on the record as having labeled myself a &lt;a href="https://ian.wold.guru/Posts/four_deeply_ingrained_csharp_cliches.html#unit-tests"&gt;unit test hater&lt;/a&gt;. This is perhaps a bit extreme taken at face value; I like to advocate for approaches which consider the pros and cons of all solutions, and matching the right solutions to the problems they best solve. More often than not, unit tests are not the right solution; largely I find they corrupt our codebases, encouraging us to twist our architectures and use poor engineering practices. To be sure though, there's instances that unit testing is appropriate.&lt;/p&gt;
&lt;p&gt;But what kind of &amp;quot;unit testing&amp;quot; is good anyway? Forgive my diversion into definitions here, but this is important: are we talking about the same thing? One engineer might take &amp;quot;unit testing&amp;quot; to mean any form of testing where I'm only testing a single function or method, while another might take the same term to mean the specific practice of abusing mocks to white box test the various code paths of each method containing business logic. Can you see my bias? The former is quite agreeable, the latter is the source of much consternation. Insofar as the latter definition is a subset of the first, then I might say that I prefer the difference of the two.&lt;/p&gt;
&lt;p&gt;Given the general kind of unit testing which is good then, we can perhaps more specifically define the proper sort. A coupling of the test itself to the implementation of the code it tests should, in all but extreme cases, be off the table. Further, &lt;a href="https://en.wikipedia.org/wiki/Data-driven_testing"&gt;example testing&lt;/a&gt; whereby we match outputs to known inputs leads to fragile tests, missed cases, and generally avoids addressing the purpose of the method in question; the proper sort of unit tests test the relationship between the input and output, not the specific cases. Given these two properties of proper unit tests, I could specifically cite forms of parameter-based testing such as &lt;a href="https://en.wikipedia.org/wiki/Property_testing"&gt;property testing&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Fuzzing"&gt;fuzz testing&lt;/a&gt; as being ideals. Some others on the edge can be interesting too: &lt;a href="https://en.wikipedia.org/wiki/Mutation_testing"&gt;mutation testing&lt;/a&gt; and contract tests both have their uses but should be strictly used within the confines of our testing rules.&lt;/p&gt;
&lt;p&gt;Now, let me be quite clear here: drawing a line in the sand saying a unit test can't be coupled to the implementation it tests means we can't use mocks or fakes to instantiate a class to test its methods. Indeed, these testing methods are probably only proper for &amp;quot;pure&amp;quot; functions. I think this derivation is a sign I'm on the right path here; I take that indication from two more fundamental principles I hold. First, our testing is in place to test the behavior of our system, not to double or triple our implementation. Second, maximizing the amount of logic in &amp;quot;pure&amp;quot; functions is good. Perhaps you disagree with me on these; that may well lead you to a very different conclusion.&lt;/p&gt;
&lt;p&gt;Understanding why we are testing - what specifically we want to gain with the tests - is perhaps the most important thing we need to sort out. As I stated, I'm interested in automated tests which verify that the system I've built satisfies the requirements it was given. Realistically, this should mean a full decoupling between the suite of tests and the code it tests. Here's the catch then: if I'm testing individual methods, I don't have total decoupling. I couldn't (or it would be quite difficult to) write my tests in one language and the system itself in another language, for a more extreme example. This is where I come in with functional testing strategies like integration tests - not that I'd necessarily want to use separate languages, but I want to achieve that sort of decoupling.&lt;/p&gt;
&lt;p&gt;This doesn't work in all cases. Can I test a mathematics class library with integration tests? Almost certainly not; testing for an example like that belongs to the domain of parameterized testing and the like. A microservice API though? I'm not sure that there's a lot here for which integration tests aren't the best and obvious choice. At least insofar as I want to test the business requirements of my system, integration tests are ideal for most of our CRUD systems. These are the tests that best let me define the constraints of the system, and that guarantee that it conforms.&lt;/p&gt;
&lt;p&gt;This brings me to the topic of BDD and TDD. You might guess that I dislike TDD, and you'd be right. Well, a bit. I dislike the idea that TDD should be prescribed. If you want to write unit tests coupled to the implementation before you write that implementation, you do you. Throw those away before you commit your changes, but bring whatever tools to the table make you the most effective engineer you can be. BDD - Behavior-Driven Development - on the other hand is something I can get behind. By my reading, BDD is just giving a name to the way I'd want to develop software anyway: that everyone developing the system should be aligned on how it's supposed to behave &lt;em&gt;before&lt;/em&gt; we implement it.&lt;/p&gt;
&lt;p&gt;It's on this subject - TDD and BDD - that I focused most of my research this month. As I outlined, it seems to me that most issues we have with our automated testing strategies is that we seem to have a tendency to stray from the fundamentals here. Before even considering automated tests, are we aware of all of the testing strategies and patterns, and the scenarios in which each works the best? Have we fully thought through &lt;em&gt;why&lt;/em&gt; we need tests and &lt;em&gt;what they provide&lt;/em&gt;, not just in general but for the specific codebase we want to test? As with any other coding tasks, we first need to approach testing from the right orientation before we can start engineering. BDD and TDD are overarching process ideas which seek to orient our approaches to testing, so I think that these and other processes are the interesting thing to consider when studying testing strategies.&lt;/p&gt;
&lt;p&gt;Finally, I feel I should touch on &lt;em&gt;code coverage&lt;/em&gt;. If my goals for my automated tests are to ensure that it meets all of its requirements, then it seems to me that I'd want some kind of metric of &amp;quot;requirements coverage&amp;quot; or the like. If my code is meeting all of its requirements - that this is genuinely a big if - then I don't really care about code coverage, do I? Take an API for example - if I have integration tests set up which cover 100% of my business cases - both happy and sad path - then code coverage is just a metric of how much code I have in my codebase that isn't getting hit. It seems to me that the utility of code coverage is not as a primary indicator of the quality of my software, but rather an incidental heuristic which I could choose to use from time to time to help refactor bits of code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Videos&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=EZ05e7EMOLM"&gt;TDD, Where Did It All Go Wrong - Ian Cooper (2017)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=IN9lftH0cJc"&gt;TDD Revisited - Ian Cooper (2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=-022ONzvQlk"&gt;TDD, BDD &amp;amp; ATDD - Allen Holub (2014)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=IYzDFHx6QPY"&gt;The lazy programmer's guide to writing thousands of tests - Scott Wlaschin (2020)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=yuEbZYKgZas"&gt;Test Driven Development: That’s Not What We Meant • Steve Freeman (2017)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gXh0iUt4TXA"&gt;An Ultimate Guide to BDD - Dave Farley (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=o-HTsJ1-wdI"&gt;STREAM VOD: ThePrimeagen vs Theo - Dev Debates on the FIRST DevHour Podcast (2022)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Writing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tyrrrz.me/blog/unit-testing-is-overrated"&gt;Unit Testing is Overrated - Oleksii Holub (2020)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://debugagent.com/why-i-dont-do-tdd"&gt;Why I Don't Do TDD - Shai Almog (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dhh.dk/2014/test-induced-design-damage.html"&gt;Test-Induced Design Damage - David Heinemeier Hansson (2014)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/TestCoverage.html"&gt;Test Coverage - Martin Fowler (2012)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last two posts are referenced (and elaborated on) by a series of conversations by Kent Beck, David Heinemeier Hansson, and Martin Fowler: &lt;a href="https://martinfowler.com/articles/is-tdd-dead/"&gt;Is TDD Dead?&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Sat, 24 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-24T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_1-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_1-2024.html</link>
      <title>Book Club 1/2024: What is a Software Architect?</title>
      <description>&lt;p&gt;This question - what is a software architect? - is a bit of an expansive one; I wouldn't think that I could ever answer it thoroughly, much less succinctly in a few article links. I do however think it's worth some time considering some of the different perspectives we encounter. I'd venture that a poll of 10 software architects would yield 11 answers to this question.&lt;/p&gt;
&lt;p&gt;Is software architecture decidedly different from software engineering? The analogy with which these titles are made - that of architects, engineers, and builders collaborating over the construction of a building - would seem to suggest so. However, a software project is decidedly different (in almost every single aspect) from a construction project. This is my opinion, but I would put it to you that while &amp;quot;software architecture&amp;quot; is probably a definable thing, a software architect is not really so different from an engineer. Indeed, I would also assert that a proper software engineer must be engaging with and actively doing software architecture as a part of their job.&lt;/p&gt;
&lt;p&gt;Should a software architect be doing anything different from an engineer? Well, perhaps not on the surface. I'd expect that an architect would be writing code about as much as someone with an engineer title, and I'd expect that an engineer would be as engaged with diagrams and ephemeral conversations about the nature of software as someone with an architect title. The separate titles then are probably most useful as a way to distinguish the primary focus that one has within a software team, and an architect would be working differently from an engineer insofar as the architecture would be their primary focus, or area of responsibility.&lt;/p&gt;
&lt;p&gt;So is it useful then to separate ourselves out with titles of &amp;quot;engineer&amp;quot; and &amp;quot;architect&amp;quot;? There are advantages to having the separate architecture title though: to motivate the team to keep architecture as a primary consideration, to be a figure of wisdom or authority with respect to the architecture, or to be the &amp;quot;point person&amp;quot; to communicate architectural concerns to other teams or other departments within a company. Perhaps it's not a useful title to have, but a separate role or &amp;quot;hat&amp;quot; which a member of a software team wears; in fact this was the way I was introduced to the concept of architecture.&lt;/p&gt;
&lt;p&gt;Having both had the title of &amp;quot;architect&amp;quot; and having been an engineer at firms which had this title, I can see its utility. To caution though: just as every piece of software, every product, and every team is different - sometimes radically different - so too are the roles of those we call architects. This is perhaps why there are so many different ideas of what architecture is, and what a proper software architect ought be. Perhaps there can't be a single, specific definition to encapsulate the idea, as the types of architecture and architectural concerns are necessarily so varied between software efforts, domains, and technologies?&lt;/p&gt;
&lt;p&gt;To try to pin some universals down, I think I could say a few definitive things about architects. Architects should be coding, and actively involved in the engineering process. Architects should primarily focus on assisting all of the members of a development team - helping to ensure alignment and stimulating everyone to think architecturally. Architects shouldn't be dictators or cudgels which make demands - a somewhat common perception which is unfortunately sometimes earned. Architects should strive to be the most approachable and collaborative members of a team.&lt;/p&gt;
&lt;p&gt;While architects should be very technically knowledgeable, I don't know that they should have all the answers when it comes to architecture itself - curiously, I think the best architects are ones which are very creative and can help brainstorm architectural ideas, the idea being that they act as an enabler of a team to define a good architecture, rather than dictating an architecture from the start. In a difficult twist, this does mean that sometimes the architect will need to furnish a specific recommendation upon request, for example for a team in a tight bind which requires a quick resolution to a difficult problem.&lt;/p&gt;
&lt;p&gt;I can hear a colleague of mine saying now: &amp;quot;This answer is so nonspecific as to be only slightly more helpful than a software architect theirself!&amp;quot;; unkind but not unfounded. I put it to you though that the composition of any software team is a jigsaw puzzle balancing who can do what with which areas of expertise are required of the technology and domain, and from team to team the jigsaw piece labeled &amp;quot;architect&amp;quot; will be a very different shape and in a very different place in the overall puzzle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=v_nhv6aY1Kg"&gt;How to Become a Great Software Architect - Eberhard Wolff (2019)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=asoVNjGoFOM"&gt;How we do Architecture at Okta - Monica Bajaj and Mark Voelker (2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nchRmYvUf2Y&amp;amp;list=PLEx5khR4g7PJELLTYwXZHcimWAwTUaWGA"&gt;Democratizing Software Architecture - Eoin Woods (2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mCM6QVHD08c"&gt;How to &amp;quot;think&amp;quot; (and design) like a Software Architect - Ron Kleinman (2019)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=6j-PyJ1tFn8"&gt;How I became a software architect... (or not) - CodeOpinion (2023)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was recently recommended Mark Richard's series &amp;quot;Software Architecture Monday&amp;quot;, which is a part of &lt;a href="https://developertoarchitect.com/"&gt;his site dedicated to &lt;em&gt;developing&lt;/em&gt; more architects&lt;/a&gt;. This series is well-thought-out, and contain a few excellent articles/videos which contain insights directly related to this topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developertoarchitect.com/lessons/lesson108.html"&gt;The Role of a Software Architect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developertoarchitect.com/lessons/lesson100.html"&gt;My Architecture Journey: Lessons Learned &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developertoarchitect.com/lessons/lesson54.html"&gt;The Software Architect's Bookshelf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developertoarchitect.com/lessons/lesson30.html"&gt;Agile and Software Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 24 Jan 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-01-24T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_1-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_1-2025.html</link>
      <title>Book Club 1/25: Results, Railways, and Decisions</title>
      <description>&lt;p&gt;I've written before on my enthusiasm for the result pattern, but I'm going to expand on that a bit here and how it relates to testing. I've spent a fair chunk of my past work-month standing up a new distributed service at work, and it's such a simple project as to almost have completely written itself, except for one opportunity I took: I wanted to see how many ways I could approach black-box testing the API. If you know me, you know I'm a big fan of writing automated tests ... in &lt;em&gt;one&lt;/em&gt; way - I really &lt;em&gt;really&lt;/em&gt; like integration-level tests, and even then just covering successful vs failing input and checking state change is robust enough for the vast majority of our systems. I usually do this and then move on to actually implementing the darn thing, and that's perfectly fine. I'm poorly-versed in other methods, and so that's what I've been reading this past month.&lt;/p&gt;
&lt;p&gt;I promise that the result and railway design patterns &lt;em&gt;do&lt;/em&gt; fit into my black-box testing ideas here; let me take a step back to motivate that. Last month, I spoke with a number of folks about the requirements of this API. It's only got a few endpoints, but there was some complexity around external systems it would need to consult - &lt;em&gt;who&lt;/em&gt; has &lt;em&gt;what&lt;/em&gt; data - so there were a number of models of the API developed before I coded anything. Goodness knows there are dozens of ways to model systems, but these (advantageously) took the form of control flow diagrams. Here's a simplified version of one for an endpoint to get a resource, which attempts to create the resource if it doesn't yet exist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the resource from the database
&lt;ul&gt;
&lt;li&gt;If it exists, go to step 4&lt;/li&gt;
&lt;li&gt;If it does not exist, proceed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Consult a cache holding some data about the resource
&lt;ul&gt;
&lt;li&gt;If there is no data in the cache, respond not found&lt;/li&gt;
&lt;li&gt;If there is data in the cache, proceed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create the resource in the database with the data from the cache
&lt;ul&gt;
&lt;li&gt;If this fails, respond unexpected error&lt;/li&gt;
&lt;li&gt;If this succeeds, proceed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Consult &lt;em&gt;another&lt;/em&gt; cache to get more data about the resource
&lt;ul&gt;
&lt;li&gt;If this succeeds, merge the datem and respond successfully&lt;/li&gt;
&lt;li&gt;If this fails, respond with the partial content from the database&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a real treat to have! For all purposes, each step represents a different result-returning method - and the results are right there, in easy-to-read markdown no less! Not only does this neatly model my results, it also neatly models the railway of the system. If you're new to &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/"&gt;the railway concept&lt;/a&gt;, it's quite straightforward. Almost all of our business logic can be thought of as a railway in that it has two tracks. There's the success track and the error track. The logic progresses through several steps along the success track, but any step might be unsuccessful and divert the control flow onto the other track. My model above is a bit more complicated because of the branching control flow and there being multiple success and failure paths, but this demonstrates the utility of this model: it keeps the extra complication straightforward and comprehensible. If I were to draw a railway diagram I might have (again, simplified):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/book-club-1-25-railway.png" alt="Railway diagram for GET endpoint" /&gt;&lt;/p&gt;
&lt;p&gt;This might be a stylistic preference of mine, but there are two things at the top of my mind when I develop business logic. The first is what the railway of the logic looks like - I encounter &lt;em&gt;incredibly&lt;/em&gt; little business logic that is not made more clear by railwaying. The second thing at the top of my mind are the results of the operations that create decision points where I might switch tracks. Those two things wholly constitute the structure of most any business logic: what tracks I have and the events that move between them. The business logic queues operations in order; those operations &lt;em&gt;operate&lt;/em&gt; and report result; the business logic decides which results, if any, change tracks; repeat. I hope I can motivate my enthusiasm at being able to have these models as an output of my engagement with the stakeholders in the system - the code really does darn-near write itself.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note: &amp;quot;railway&amp;quot; is more of a way of thinking, not really a pattern and not really an orientation. Maybe I could say &amp;quot;the rail...way of thinking!&amp;quot; Please laugh.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This brings me to testing, and a hypothesis: I figure that just as these models were able to prefigure the structure of the system's code, so too they would prefigure its tests. This seems somewhat obvious: the model is a graph and by traversing each possible path through the graph I can generate all of the test scenarios necessary to cover all of the branches in control flow and ensure proper responses. For the graph I've given you it's quite easy to do this manually, however larger graphs whose nodes have more results (e.g. different types of successes or failures) will become unwieldy. This is the main thrust of my research this month, with which I &lt;a href="https://github.com/IanWold/CFWeaver"&gt;hacked together a tool&lt;/a&gt; to generate test scenarios for me from these shared models.&lt;/p&gt;
&lt;p&gt;Owing to the large number of different ways we can diagram our systems, there are an equally large number of ways to generate test scenarios from these models. A collection of knowledge on this sort of practice can be found under the term &lt;a href="https://en.wikipedia.org/wiki/Model-based_testing"&gt;model-based testing&lt;/a&gt;. This sort of approach certainly has limits though. In my case the models were a product of the &lt;em&gt;kind&lt;/em&gt; of collaboration I ended up doing for this project, and surely the utility of the specific testing approach I chose here is also influenced by the architecture of the broader system and the culture at my firm. Indeed, the approach might have to be quite different (or not used at all) on other projects.&lt;/p&gt;
&lt;p&gt;It is a very interesting consideration though, along with the rest of the resources available for test generation. I'm quite contented by the results of my exploration here; it's given me a more robust and flexible framework to approach testing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My related writing:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/roll_your_own_csharp_results.html"&gt;Roll Your Own C# Results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/testing_logging_in_asp_net_core.html"&gt;Testing Logging in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/its_okay_to_be_a_bit_techy_in_your_gherkin.html"&gt;It's Okay to be a Bit Techy in Your Gherkin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/guerrila_devex_testing.html"&gt;Guerrila DevEx Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/book_club_10-2023.html"&gt;Book Club 10/2023: Functional Patterns in C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/book_club-2-2024.html"&gt;Book Club 2/2024: Recovering from TDD and Unit Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;On results and railways:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern"&gt;Functional Error Handling in .NET With the Result Pattern - Milan Jovanović&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://andrewlock.net/working-with-the-result-pattern-part-4-is-the-result-pattern-worth-it/"&gt;Is the result pattern worth it? - Andrew Lock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://threenine.blog/posts/operation-result-pattern"&gt;The Operation Result Pattern - Gary Woodfine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A final note on the result pattern (on which I should probably elaborate in a future article) is its utility lies in modeling &lt;em&gt;expected&lt;/em&gt; failure cases which have associated business logic. This is a distinction between bug, error, failure, or the other sorts of paths the code might take.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/cs/software-testing-defect-bug-error-and-failure"&gt;Software Testing: Defect, Bug, Error, and Failure - Rahmat Ullah Orakzai&lt;/a&gt; gives a good overview of this&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=936191"&gt;Bug, Fault, Error, or Weakness: Demystifying Software Security Vulnerabilities - Irena Bojanova and Carlos Eduardo C. Galhardo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joeduffyblog.com/2016/02/07/the-error-model/"&gt;The Error Model - Joe Duffy&lt;/a&gt; gives my favorite explanation and prescribed solutions here&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don't know of a better overview of the railway concept than that from Scott Wlaschin. He's also written a bit on property testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/"&gt;Railway oriented programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/series/property-based-testing/"&gt;The &amp;quot;Property Based Testing&amp;quot; series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;On testing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.geeksforgeeks.org/model-based-testing-in-software-testing/"&gt;Geeks for Geeks&lt;/a&gt; gives an acceptable overview of concepts&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.geeksforgeeks.org/control-flow-software-testing/"&gt;Control Flow Software Testing - Geeks for Geeks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://torxakis.org/userdocs/stable/mbt/mbt.html"&gt;Introduction to Model-Based Testing by TorXakis&lt;/a&gt; begins a large repository of knowledge on the topic&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=152162"&gt;Automated Combinatorial Test Methods – Beyond Pairwise Testing - D. Richard Kuhn, Dr. Raghu Kacker, and Dr. Yu Lei&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/projects/automated-combinatorial-testing-for-software"&gt;Combinatorial Methods for Trust and Assurance - NIST&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 27 Jan 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-01-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_1-2026</guid>
      <link>https://ian.wold.guru/Posts/book_club_1-2026.html</link>
      <title>Book Club 1/2026: C#, Without Allocation</title>
      <description>&lt;p&gt;I'm not &lt;em&gt;very&lt;/em&gt; good at coding for performance. I can do some rudimentary profiling, and if you give me a couple metrics to optimize for I can do a pretty good job getting those numbers down. When I sit down to write a piece of software though, performance is maybe my second or third concern, after ensuring the software works properly, it's well-formed (for readability and extensibility), it's well-documented, I can deploy it easily, and the like. Intentionally, I write software both in a language/runtime and in the sorts of domains for which this approach works.&lt;/p&gt;
&lt;p&gt;I don't think that every engineer on a team needs to be extremely performance-focused. We have colleagues who are fantastic at performance, and I hold their contributions to a high esteem. At the same time though, it's no good allowing anyone to think it's alright for an engineer to neglect developing a decent knowledge of engineering for performance, and it should especially be discouraged for anyone to not consider performance altogether. The worst offense would be to develop a habit of slopping performance concerns on our performance colleague; often code written without an eye towards performance is code that &lt;em&gt;can't&lt;/em&gt; be made more performant.&lt;/p&gt;
&lt;p&gt;I know plenty of folks will disagree with my enumerating &amp;quot;performance&amp;quot; among the sorts of &lt;em&gt;skills&lt;/em&gt; that software engineers can have - for the above reason no less - but the fact is that there are many different sorts of considerations that need to be held in the head of a software engineer, and it's natural that we will each be good at some considerations but not all of them. Again, this is not to give anyone a reason to neglect performance. Instead, this presents an opportunity to ask: &lt;em&gt;how frequently do you review your own code through a performance lens?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My answer is &amp;quot;not as frequently as I'd like,&amp;quot; which I think is a good opportunity to practice more performant development. One aspect of writing performant code, particularly in a garbage-collected language like C#, is to consider how the runtime allocates data. Reducing allocations in languages like C# or Java isn't just about making sure less memory is used - more memory use &lt;em&gt;also&lt;/em&gt; increases garbage collector time, which can result in its own set of difficult-to-debug issues if left unchecked. Using the wrong kind of object (&lt;code&gt;class&lt;/code&gt;/&lt;code&gt;struct&lt;/code&gt;) depending on your use case can cause copying where there doesn't need to be any.&lt;/p&gt;
&lt;p&gt;Fortunately, we don't need to go looking through IL to be able to learn some fundamentals that can significantly help us here! I've collected some resources on writing allocation-free C#. Notice: there's a lot of repeated information between the sources isn't there?&lt;/p&gt;
&lt;p&gt;Before listing these links though, I want to point everyone to one of my favorite C# engineers, though I have never spoken with them. &lt;a href="https://github.com/neuecc"&gt;Yoshifumi Kawai (@neuecc)&lt;/a&gt; and their company &lt;a href="https://github.com/Cysharp"&gt;Cysharp&lt;/a&gt; have published a wealth of open-source projects that provide a master class in practical, performant C#. From a &lt;a href="https://github.com/Cysharp/ZLinq"&gt;zero-allocation LINQ implementation&lt;/a&gt; to maybe the fastest &lt;a href="https://github.com/Cysharp/MemoryPack"&gt;binary (de)serializer&lt;/a&gt; for .NET, I am a huge fan.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/performance/"&gt;Reduce memory allocations using new C# features - MSDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.maartenballiauw.be/posts/2016-10-19-making-net-code-less-allocatey-garbage-collector/"&gt;Making .NET code less allocatey - Allocations and the Garbage Collector - Maarten Balliauw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://endjin.com/blog/2023/09/optimising-dotnet-code-2-hunting-for-allocations"&gt;Optimising .NET code: Hunting for allocations - Jonathan George&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/c-sharp-programming/zero-allocation-patterns-in-modern-net-web-apis-8c06c18743b4"&gt;Zero-Allocation Patterns in Modern .NET Web APIs - Sukhpinder Singh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sebaslab.com/zero-allocation-code-in-unity/"&gt;Zero allocation code in C# and Unity - Sebastiano Mandalà&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nazarovsa/csharp-zero-allocation/blob/main/Presentation.pdf"&gt;Writing zero-allocation code with C# - Sergey Nazarov&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Writing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nK54s84xRRs"&gt;Writing Allocation Free Code in C# - Matt Ellis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=B2yOjLyEZk0"&gt;Writing C# without allocating ANY memory - Nick Chapsas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mLX1sYVf-Xg"&gt;Pushing C# to the limit - Joe Albahari&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=9FEfy9y0fFQ"&gt;Exploring NET's Memory Management — A Trip Down Memory Lane - Maarten Balliauw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;And a preview of more performance things...&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=3r6gbZFRDHs"&gt;High-performance code design patterns in C# - Konrad Kokosa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sun, 25 Jan 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-01-25T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_10-11-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_10-11-2025.html</link>
      <title>Book Club 10&amp;11/2025: .NET 10, C# 14</title>
      <description>&lt;p&gt;Happy Turkey Day! Last year when I published my newsletter on Thanksgiving, &lt;a href="https://ian.wold.guru/Posts/book_club_11-2024.html"&gt;I was thankful for my colleagues who make a habit of saying &amp;quot;no.&amp;quot;&lt;/a&gt;. This year I'm thankful for my colleagues who keep .NET applications updated to the latest versions; it's not just a burden to have to maintain many codebases at different versions, but each new .NET is a significant improvement over the last. .NET Framework had cultivated a well-earned reputation as a bogged-down runtime reached for by large enterprises who care more about their relationship with Microsoft than the quality of their line-of-business software. It could be said that .NET was born as MS-flavored Java and stayed there.&lt;/p&gt;
&lt;p&gt;.NET (&lt;em&gt;just&lt;/em&gt; net, not &amp;quot;framework&amp;quot; or &amp;quot;core&amp;quot;) has, I think, completely lifted away from this problem. Not just multi-platform and AOT support, but performance optimizations and the inclusion of libraries that significantly improve development capabilities in networking, security, and performance, combined with an explosion of features in C# and F#, have put the ecosystem on-par with the best-optimized runtimes/languages. It seems more that Java is playing catch-up to .NET anymore. Great success!&lt;/p&gt;
&lt;p&gt;The latest release this month, .NET 10, continues all of that but at a manageable pace. Microsoft has been sprinting with recent releases that have dramatically altered the .NET world, but .NET 10 is not shaking anything up that much ... on the surface.&lt;/p&gt;
&lt;p&gt;A couple important developments though. First, they're calling Aspire just &amp;quot;Aspire&amp;quot; now, not &amp;quot;Aspire.NET&amp;quot;; it appears that they're attempting to develop this into a multi-language tool with utility for folks outside the .NET ecosystem. Reading the tea leaves from many miles away I might guess that Microsoft wants to create a platform for &lt;em&gt;any&lt;/em&gt; engineer to consider for distributed application development, and this would make sense with my understanding of Aspire being a plot to sell more Azure. Helpfully though, with .NET 10 allowing us to run single-file C# applications, Aspire can be segregated to some single file instead of needing a whole CSPROJ and separate directory and the like.&lt;/p&gt;
&lt;p&gt;Speaking of running single files, yes! This is important for my &lt;a href="https://github.com/IanWold/Metalsharp"&gt;Metalsharp&lt;/a&gt; tool that generates my blog; I will be moving this forward to version 1.0.0 soon! You can see an (admittedly somewhat complex) example of a single file app &lt;a href="https://github.com/IanWold/ianwold.github.io/blob/master/build.cs"&gt;on my blog's repo&lt;/a&gt;. The fun bits are at the top.&lt;/p&gt;
&lt;p&gt;C# got a new &lt;code&gt;field&lt;/code&gt; keyword exposing the compiler-generated field on each auto property. Finally. This should have been included along with auto properties back in the day but what can you do. More interesting though, C# has an &lt;code&gt;extension&lt;/code&gt; keyword now:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class Extensions {
    // New extension keyword groups a bunch of extensions together for a type
    extension(SomeType thing) {
        // &amp;quot;static&amp;quot; and &amp;quot;this&amp;quot; are not needed!
        public string GetHello() =&amp;gt; &amp;quot;Hi&amp;quot;;

        // Whoa look it's an extension property!
        public string SayHello =&amp;gt; &amp;quot;Hello&amp;quot;;
    }

    // But wait there's more! What if I don't name the extension argument?
    extension(SomeType) {
        // Whoa look, static extensions!
        public static string Hello =&amp;gt; &amp;quot;Howdy&amp;quot;;
    }
}

// Outputs:
// Howdy
// Hello
// Hi
public void TestExtensions() {
    Console.WriteLine(SomeType.Hello);

    var thing = new SomeType();
    Console.WriteLine(thing.SayHello);
    Console.WriteLine(thing.GetHello());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This feature is really cool and seems innocuous. Well, in fact this is an important step towards what the C# team has called &amp;quot;extension everything.&amp;quot; The idea is that everything should be able to be extended at any scope, and it's an idea that goes back to the beginning of .NET Core. The language team has been very slow and deliberate in considering this feature, and I expect it to be roled out bit-by-bit in many versions. This is the first step to that.&lt;/p&gt;
&lt;p&gt;Also absent from this release: discriminated unions! Not my favorite feature but my most-needed feature for so long. There's been important developments from the language team on this front though and I expect to see the first iteration of these roll out next year. I am impatient though so I wrote a source generator and analyzer as a proof-of-concept so I can have actual unions now, which I've aptly named &lt;a href="https://github.com/IanWold/UnionizeNow"&gt;UnionizeNow&lt;/a&gt;. Full explanation and kind of a blog post in that repo. It's very cool! I might not make this into a full Nuget package since unions are, in theory, around the corner, but two important things for me with it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It frees me to start developing union-dependent ideas in a way that will be mostly compatible with the unions that the language team will roll out (their thinking is far enough along to give confidence here), and&lt;/li&gt;
&lt;li&gt;If the first iteration of unions are lacking some small features which I need, I hope I could easily retool this generator to graft those on.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's more reading on new .NET:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/dotnet_from_framework_to_10.html"&gt;.NET: From Framework to 10 - Ian Wold&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/"&gt;Performance Improvements in .NET 10 - Stephen Toub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bytehide.com/blog/whats-new-in-dotnet-10"&gt;What’s New in .NET 10: Everything You Need to Know - ByteHide&lt;/a&gt; (collects all the little bits)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.roundthecode.com/dotnet-tutorials/are-c-sharp-14-new-features-worth-updating-app-dotnet-10"&gt;Are C# 14's new features worth updating your app to .NET 10? - Round the Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=x0725PDUho8"&gt;The Coolest Feature of .NET 10 is Here - Nick Chapsas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Thu, 27 Nov 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-11-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_10-2023</guid>
      <link>https://ian.wold.guru/Posts/book_club_10-2023.html</link>
      <title>Book Club 10/2023: Functional Patterns in C#</title>
      <description>&lt;p&gt;Happy spooky season!&lt;/p&gt;
&lt;p&gt;This month I've focused on functional domain modeling and related patterns. We're just a few weeks away from the release of the next version of C#, and like each previous version it'll introduce even more functional features. We still aren't getting discriminated unions, but as C# becomes more functional, these patterns are becoming increasingly more attractive. Traditionally, C# is written in OO or procedural styles, and from my perspective there doesn't seem to be a great deal of discussion among C# engineers about incorporating functional patterns. Maybe you run in different circles, but I think there's room for improvement across the board here. Even the result monad, which can be used within an entirely OO context, is infrequently implemented.&lt;/p&gt;
&lt;p&gt;I think it's important to be discussing functional patterns in C#, for a few practical reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eventually we're getting DU, and that's going to change a lot of things&lt;/li&gt;
&lt;li&gt;Our F# colleagues are doing great work in this area, we should engage them more&lt;/li&gt;
&lt;li&gt;Using functional patterns is cool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But above all, if C# supports these patterns and they can help us write better code, &lt;em&gt;why would we neglect them&lt;/em&gt;? To explore this topic, I've curated a set of talks by Scott Wlaschin and Mark Seemann who both do a great job explaining functional programming from a conceptual perspective, demonstrating its power in F#, and ultimately demonstrating C# equivalents.&lt;/p&gt;
&lt;p&gt;I hope these talks make you all as excited for discriminated unions in C# as I am!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=2JB1_e5wZmU"&gt;Domain Modeling Made Functional - Scott Wlaschin (2019)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=srQt1NAHYC0"&gt;Functional Design Patterns - Scott Wlaschin (2017)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WhEkBCWpDas"&gt;The Power of Composition - Scott Wlaschin (2018)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=IYzDFHx6QPY"&gt;The Lazy Programmer's Guide to Writing Thousands of Tests - Scott Wlaschin (2020)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=US8QG9I1XW0"&gt;Functional Architecture - The Pits of Success - Mark Seemann (2016)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=F9bznonKc64"&gt;Get Value out of Your Monad - Mark Seemann (2018)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=cxs7oLGrxQ4"&gt;From Dependency Injection to Dependency Rejection - Mark Seemann (2017)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=qBYVW4ghMi8"&gt;Dependency Injection Revisited - Mark Seemann (2018)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 27 Oct 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-10-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_10-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_10-2024.html</link>
      <title>Book Club 10/2024: Fallacies of Distributed Computing</title>
      <description>&lt;p&gt;Happy Halloween! Boo! I'm celebrating now for two reasons. First, the weather is very nice where I'm at; I prefer wearing more layers and Fall is the perfect month for that. Second, I just finished my &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;series on the fallacies of distributed computing&lt;/a&gt; - perhaps the world didn't really need it but I was here to provide it.&lt;/p&gt;
&lt;p&gt;Much earlier this year I figured that writing this would be a good way to exercise the writey muscles and allow me to express some thoughts on a topic I surely have some thoughts on. I think I achieved &lt;em&gt;those&lt;/em&gt; goals, though perhaps such endeavors in the future could focus on being more interesting. This is a relatively dry topic as it stands, and I don't know that I put a lot of creativity into it. Nonetheless, while this is a frequently-commented-on topic, I feel that I was able to go more in-depth on a number of points which are lacking in other writing.&lt;/p&gt;
&lt;p&gt;In particular, a lot of writing on this subject doesn't emphasize the difficulty of distributing. It's not just non-trivial; I'm firmly committed to the position that a distributed system has 10x as many &lt;em&gt;things&lt;/em&gt; as monolithic systems - 10x the number of bugs, 10x the amount of development effort, 10x the number of meetings (both to plan and to figure out what's going on), 10x the maintenance cost, and so on. To me, this is the implication of the fallacies: there's a ton of work you can't forget to do. Why would anyone ever distribute their system?&lt;/p&gt;
&lt;p&gt;Of course, there are problems that are unsolvable but by distribution. It's the only option if you need to independently scale some component, which perhaps &lt;em&gt;any&lt;/em&gt; system at a sufficient size will need to do. Before we can ask how to distribute a system, we need to ask whether we should. Once we've identified a need for a discrete component to be distributed, then we need to go about the &lt;em&gt;how&lt;/em&gt;. That's going to be different in different cases, and the fallacies do a great job, in my mind, of guiding our conversation around that question. I didn't prescribe a lot of &amp;quot;do this and that&amp;quot; in my series because I don't see that as the point. It's the mindset that's the point, the architectural mindset: how do I consider all the tradeoffs there are?&lt;/p&gt;
&lt;p&gt;Being clear, there are major tradeoffs. You can't solve for all of the potential issues. Look back at latency and bandwidth: these two are related and trying to reduce one will (almost certainly) increase the other, unless we can consume more resources. We can only consume more resources to a certain point, so what gives? Well, nothing gives. You're going to be vulnerable somewhere, and you can choose where you want that. This is, of course, the point to reiterate that the going advice ought be that we shouldn't distribute in the first place unless it's absolutely necessary.&lt;/p&gt;
&lt;p&gt;So the best &lt;em&gt;default&lt;/em&gt; we have in doing development is to create a well-architected monolith. One with really good principles and domain segregation. Yeah that's tough, but you're not decreasing &lt;em&gt;anything&lt;/em&gt; by distributing - especially if you look to it as your savior. With load there'll come components which require distribution, and we separate those out at this point. This architectural prescription will (probably) not ever result in a microservices system. Rather, we'll end up with a very dapper trunk-and-branch architecture, where we have a monolithic trunk surrounded by individually-distributed branches. You might have a &amp;quot;leaf&amp;quot; layer of common services, and that's it! Keep the possible paths through the system shallow. Easy monitoring, easy debugging, you can keep everything under control.&lt;/p&gt;
&lt;p&gt;The distribution-skeptical monolith-to-trunk-and-branch engineer will typically architect a more resilient and adaptable system than the distribution-zealous microservices engineer. I wonder if coming years will be characterized by an equal zeal for the &amp;quot;modular monolith&amp;quot;, which would end up being - to my estimation - hilarious. Ours is an industry continuously held hostage by trends.&lt;/p&gt;
&lt;p&gt;Today I'll share a bunch of related links, particularly ones which came up in research for this series. It's helpful to see what others are writing, both to get a sense of the area and to avoid repeating what others have offered. Particular Software, which maintains the popular NServiceBus, keeps a blog that's worth reading. Naturally they also have a series on the Fallacies, and while sparse it's a good resource. I was particularly influenced by their &lt;a href="https://particular.net/blog/the-network-is-homogeneous"&gt;final post on the eighth fallacy&lt;/a&gt;, which enlightened me to the idea that the eighth fallacy can look at every aspect of heterogeneity between system components.&lt;/p&gt;
&lt;p&gt;Particular is run by Udi Dahan, who has given a number of talks on distributed computing in the .NET world, and &lt;a href="https://www.youtube.com/watch?v=8fRzZtJ_SLk&amp;amp;list=PL1DZqeVwRLnD3EjyciYAO82dT9Owiq8I5"&gt;has a series focusing on the fallacies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other writing on the fallacies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pages.cs.wisc.edu/%7Ezuyu/files/fallacies.pdf"&gt;Fallacies of Distributed Computing Explained - Arnon Rotem-Gal-Oz&lt;/a&gt;, a paper from the University of Wisconsin that seems to be frequently shared.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ryan-french.medium.com/the-fallacies-of-distributed-computing-latency-is-zero-14a02a73f43a"&gt;The Fallacies of Distributed Computing — Latency is Zero - Ryan French&lt;/a&gt; seems to be part of an unfinished series.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cacm.acm.org/practice/the-network-is-reliable/"&gt;The Network Is Reliable - Peter Bailis and Kyle Kingsbury&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dzone.com/articles/a-guide-to-managing-the-first-fallacy-of-distribut"&gt;A Guide to Managing the First Fallacy of Distributed Computing - Anadi Misra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://newsletter.techworld-with-milan.com/p/what-are-the-main-cloud-design-patterns"&gt;What are the main Cloud Design Patterns? - Milan Milanović&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think Renegade Otter's article &lt;a href="https://renegadeotter.com/2023/09/10/death-by-a-thousand-microservices.html"&gt;Death by a Thousand Microservices&lt;/a&gt; is one of those articles we'll still be sharing in ten years. The ugliness created by the zealous pursuit of distribution is described well here. My favorite observation from that article is that Shopify is a Rails monolith (&lt;em&gt;presumably&lt;/em&gt; a trunk with branches) and has a revenue in the several billions.&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href="https://developertoarchitect.com/"&gt;Mark Richards&lt;/a&gt; is an architect who has made a fair career out of being good at explaining fundamental concepts in architecture. Two recent talks of his which I think are particularly good and pertinent to this topic are &lt;a href="https://www.youtube.com/watch?v=1kESDzfEaxo"&gt;Elements of Distributed Architecture&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=wiWjX9yaXTY"&gt;Decomposition Patterns&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Mon, 28 Oct 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-10-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_11-2023</guid>
      <link>https://ian.wold.guru/Posts/book_club_11-2023.html</link>
      <title>Book Club 11/2023: New .NET, New C#</title>
      <description>&lt;p&gt;I'm looking forward to turkey day tomorrow gobble gobble! This year I'm thankful that I work in ecommerce so I get to have a peaceful extended weekend because nobody visits ecommerce sites on Thanksgiving weekend. At least that's what they told me in the interview before they hired me. &lt;em&gt;Insert joke about how it's better to be working in ecommerce than at OpenAI this weekend regardless...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Anyway, last week we got the new release of .NET, which brings langauge and tooling updates across the board, so I want to focus on some of those fun things.&lt;/p&gt;
&lt;p&gt;First, Blazor has taken the third (or is it fourth now) of the 1,000 steps it needs to take to become a viable platform for SAAS, with hybrid client/server rendering. I don't have a lot to say there, but I use Blazor for a number of personal projects when I need to quickly draw up a UI to look into some .NET backend scenario or another.&lt;/p&gt;
&lt;p&gt;Since Microsoft started down the path of .NET Core, the whole ecosystem has been embracing OSS and free software in a way that's completely rewritten the whole modus operandi of Microsoft under Nadella. Indeed, it seems like at this point in time, you can use .NET without a single worry about vendor lock-in. Well, Microsoft is here to save you from that horrible wasteland of unrestricted freedom with &lt;a href="https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire-simplifying-cloud-native-development-with-dotnet-8/"&gt;.NET Aspire&lt;/a&gt;. Nevermind that &lt;a href="https://www.fearofoblivion.com/build-a-modular-monolith-first"&gt;you probably don't need a distributed system&lt;/a&gt;, and even if you did &lt;a href="https://renegadeotter.com/2023/09/10/death-by-a-thousand-microservices.html"&gt;you almost certainly don't need microservices&lt;/a&gt;, they have cloud computes to sell you! Aspire makes it easy to avoid footgunning yourself as you begin your next project distributed from the start by skipping you right to the step where you blow your foot off with a bazooka - all hail the mighty Azure! Or, you know, if like 99.99% of all apps out there you'd be fine with it deployed in a Docker container with a couple of related services, &lt;a href="https://ian.wold.guru/Posts/deploying_aspdotnet_7_projects_with_railway.html"&gt;you could just use Railway&lt;/a&gt;. &lt;em&gt;Note that I'm definitely queuing up an article on using Aspire with Railway despite my skepticism that Aspire is a good idea.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12"&gt;C# hasn't gotten too many updates&lt;/a&gt;, but we have two syntactical updates that are essential and should have been included much earlier: collection expressions and primary constructors for classes.&lt;/p&gt;
&lt;p&gt;Collection expressions, or perhaps &amp;quot;enumerable literals&amp;quot;, should have been a part of the language from the start, and you should convert all of your code over to using these.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var list = [1, 2, 3, 4, 5];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Records have had primary constructors since they were introduced, and I think that was largely related to the desire to have tuple interop (is interop the right word here?), but now classes have them too &lt;em&gt;except quite different&lt;/em&gt;. The parameters in a primary constructor for a class are, more logically than records, private members of that class, significantly reducing the amount of boilerplate &lt;a href="https://ian.wold.guru/Posts/book_club_10-2023.html"&gt;if you're still using dependency injection&lt;/a&gt;. It's been how many years since Scala came out, but now we can be one of the cool kids on the block too! Right?&lt;/p&gt;
&lt;p&gt;Anyway, I'll just leave you with a few talks from the .NET conference with some of the other tidbits that should be used in .NET going forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/BnjHArsYGLM?si=NsnqXLMKwcmirGZM"&gt;Improving your application telemetry using .NET 8 and Open Telemetry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/FpQXyFoZ9aY?si=qhDqySjMAOrxa_9x"&gt;Tiny, fast ASP.NET Core APIs with native AOT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/qXsRz0YWvu4?si=p9oaPMq8h4an1Fq5"&gt;From IL Weaving to Source Generators, the Realm story&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/Yf8t7GqA6zA?si=WoidTSJRaUe4be-0"&gt;All About C# Source Generators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then a couple of talks from the lead designers of C# and F# regarding the history and direction of each language:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=v8bqAm4aUFM"&gt;Where's C# Headed? - Mads Torgersen (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=CLKZ7ZgVido"&gt;The Functional Journey of C# - Mads Torgersen (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sC0HUq2KkFc"&gt;The F# Path to Relaxation - Don Syme (2021)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=MXKM5dSk_8o"&gt;What's new in F# 5 &amp;amp; 6 - Don Syme (2021)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 22 Nov 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-11-22T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_11-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_11-2024.html</link>
      <title>Book Club 11/2024: No</title>
      <description>&lt;p&gt;Happy Turkey Day gobble gobble! Those of us in the US are celebrating Thanksgiving Day, and today I'm thankful for all my colleagues who make a habit of saying &amp;quot;no&amp;quot;. Does the period go inside the &lt;em&gt;&amp;quot;&lt;/em&gt; in this case? I've always been unclear.&lt;/p&gt;
&lt;p&gt;Anyway, I'm not a big fan of &amp;quot;yes&amp;quot; - &amp;quot;yes, we can add all those features&amp;quot;, &amp;quot;sure you can add those extra layers to the architecture&amp;quot;, &amp;quot;Decompose our microservices into nanoservices in spite of only having 12 users? You betcha!&amp;quot; These are kind of silly examples admittedly but it's a common picture that's been painted by plenty of opinion-havers in our industry before me. This isn't a problem unique to ours too, the so-called yes men are everywhere.&lt;/p&gt;
&lt;p&gt;I spend a lot of time speaking with my colleagues about the importance of qualifying knowledge, learnings, and opinions with conditions; too many of us are too absolute in our thinking. Instead of holding the opinion &amp;quot;we should &lt;em&gt;always&lt;/em&gt; use onion architecture&amp;quot; it's better to assert &amp;quot;&lt;em&gt;if&lt;/em&gt; we have a large monolith that requires robust domain segregation, &lt;em&gt;then&lt;/em&gt; onion architecture is preferred.&amp;quot; Whether you hold either opinion or not, I hope it's obvious that we would all prefer to work with the engineer holding the second. When we learn new things or develop new strategies or &lt;em&gt;whathaveyou&lt;/em&gt;, the success always happened in the context of some environment, and the facts of that context are as much a part of the success as the actual good thing that was implemented. Our work is more robust when we incorporate those facts into our learnings in these qualifying conditions.&lt;/p&gt;
&lt;p&gt;This is a way of not saying &amp;quot;yes&amp;quot; at each opportunity. In my example, we change from &amp;quot;yes, let's use onion architecture&amp;quot; to &amp;quot;maybe, let's see if we should use onion architecture.&amp;quot; The same comes from the other direction with &amp;quot;no&amp;quot; statements. Think of all the software engineering opinions you hold, and let me ask you how many are &amp;quot;no&amp;quot; opinions? I mean propositions like &amp;quot;don't use onion architecture&amp;quot; or &amp;quot;don't put opening curlies on the same line&amp;quot; or the like. Now of those &amp;quot;no&amp;quot; opinions you have, how many of them are just the opposite of a concrete &amp;quot;yes&amp;quot; opinion you hold? I mean, for example, if you hold the opinion &amp;quot;don't put opening curlies on the same line&amp;quot; you also think &amp;quot;always put opening curlies on the next line.&amp;quot; When I did this exercise I found that a lot of my &amp;quot;no&amp;quot; opinions are just mirrors of other &amp;quot;yeses.&amp;quot;&lt;/p&gt;
&lt;p&gt;These are equally unproductive, and transforming them into conditionals will do good. It's clear then that offering a &amp;quot;no&amp;quot; opinion, or saying &amp;quot;no&amp;quot; in any way, is always best served by making sure it's the right &amp;quot;no&amp;quot; for the context, and being open to the possibility of a &amp;quot;yes&amp;quot; instead. That said, there's infinitely more &amp;quot;no&amp;quot; than &amp;quot;yes&amp;quot; to be had at any given time. Think about the last trivial bug you had to solve at work - there's a million choices you could take that are obviously wrong. You didn't need to rewrite the whole application, you didn't need to distribute it out into a separate service, you didn't need to introduce a cache. Well, probably not for all of those, but you get the point: there are very few good paths and an infinite number of bad paths.&lt;/p&gt;
&lt;p&gt;So &amp;quot;no&amp;quot; positions are significantly more common, and you're going to be in a better position defaulting to &amp;quot;no.&amp;quot; This comes back around to our starting point being annoyed by our yes-colleagues: if &lt;em&gt;most&lt;/em&gt; paths before us are bad, they get us into trouble indiscriminately saying yes. If only a few paths are good paths forward, skepticism and carefulness behoove us. Being good at saying &amp;quot;no&amp;quot; helps to keep us on the right course.&lt;/p&gt;
&lt;p&gt;On a final note, I've heard from a few folks that their interest in the topic of &amp;quot;no&amp;quot; is more inclined towards a largely unexplored field of ethics within software engineering. I think there's a growing sense that we need some manner of organization in this area; our industry has kind of been flying by the seat of its pants for decades, and almost every other profession is better organized with a proper code of ethics. I can see how the &amp;quot;no&amp;quot; topic fits in here: what are our ethical obligations as to when to say &amp;quot;no?&amp;quot; I'll confess that I'm not terribly interested in this question at the moment, I was more interested in exploring how &amp;quot;no&amp;quot; can shape our outlook on the problem space, and the space itself. Certainly though I'll link some resources below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://adamralph.com/2019/10/22/would-it-help-if-i-spoke-to-your-management/"&gt;Would it help if I spoke to your management? - Adam Ralph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/conviction/"&gt;No one can teach you to have conviction - Ben Kuhn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grugbrain.dev/#grug-on-saying-no"&gt;Grug on Saying No&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dzone.com/articles/the-power-of-saying-no-a-superpower"&gt;Saying &amp;quot;NO&amp;quot; - A Superpower - Anand Safi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lifehacker.com/im-david-heinemeier-hansson-basecamp-cto-and-this-is-1820470919"&gt;I'm David Heinemeier Hansson, Basecamp CTO, and This Is How I Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lucasfcosta.com/2020/09/05/not-having-problems.html"&gt;Not Having Problems - Lucas F. Costa&lt;/a&gt; (loosely related)&lt;/li&gt;
&lt;li&gt;Finally a general link to &lt;a href="https://renegadeotter.com/"&gt;Renegade Otter&lt;/a&gt;, where each blog post is a &amp;quot;no&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Video/Podcast:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=pcGW-FiapG8"&gt;Say No by Default - REWORK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=H8eP99neOVs"&gt;Focusing is about saying no - Steve Jobs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On Ethics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uncle Bob has an interest in this space:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4T0ivYGSNpg"&gt;Software Engineering Ethics Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Tng6Fox8EfI"&gt;The Scribe's Oath&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=A5umy4lUOOY"&gt;So You Can Sleep At Night - Jonathan Rothwell and Steve Freeman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mw-OAGCcmSA"&gt;&amp;quot;I'm sorry Dave, I can't do that&amp;quot;: Ethics in Software Development - Dr. Morgan Leigh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=___k3hCQHEU"&gt;Software development, responsibility and ethics: the coming crisis - Richard Fontana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Thu, 28 Nov 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-11-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_12-2023</guid>
      <link>https://ian.wold.guru/Posts/book_club_12-2023.html</link>
      <title>Book Club 12/2023: Workflow, Process, and Agile</title>
      <description>&lt;p&gt;Ho ho ho merry book club day - I'm sending this one out a bit early; tomorrow I'm headed on vacation and won't be back until the new year. Preemptively thinking about the new year and resolutions, I'm thinking about changes in workflow and process. How can we develop software on our own, on our teams, and in our organizations?&lt;/p&gt;
&lt;p&gt;Several years ago a group of smart engineers drafted the &lt;a href="https://agilemanifesto.org/"&gt;Agile Manifesto&lt;/a&gt; to take a stab at figuring out the best way to develop software. I've yet to see a better summation of best practices than the &lt;a href="https://agilemanifesto.org/principles.html"&gt;12 Agile Principles&lt;/a&gt;, however they end up being kind of vague and non-clear, almost like a fortune cookie or today's horoscope.&lt;/p&gt;
&lt;p&gt;A lot of folks since have tried to come up with more concrete principles built on top of Agile. The most famous (or perhaps infamous) one is Scrum. That one is so common that today &amp;quot;Agile&amp;quot; is used interchangeably with &amp;quot;Scrum&amp;quot;. However, I challenge you to read the Agile manifesto and principles and the &lt;a href="https://scrumguides.org/scrum-guide.html"&gt;Scrum guide&lt;/a&gt; and tell me that these look like they're actually trying to do the same thing. They're not. Scrum, at it's best, is a horrible corruption of Agile to try to shoehorn a better quality of life for software engineers into big corporate environments. At it's worst Scrum is a malicious attempt to keep a whole cadre of &amp;quot;scrum masters&amp;quot;, &amp;quot;agile coaches&amp;quot;, and other similar folks employed in cushy corporate jobs.&lt;/p&gt;
&lt;p&gt;Agile, in spite of its generality, is the best starting point I know of to try to solve how to develop software, and Scrum ain't it. At the core, what I've always tried to stick to from the Agile thinking is to deliver value early and often, change the actual process itself as the team and product evolve, and eliminate any blockers to being able to do those two. While I think highest of Agile, I have several other bits and pieces in my toolbelt here. I like a lot of things about the &lt;a href="https://en.wikipedia.org/wiki/Kanban_(development)"&gt;Kanban process&lt;/a&gt;, and I &lt;a href="https://ian.wold.guru/Posts/daily_grug.html"&gt;start my day with a Grug quote&lt;/a&gt;, among other smaller bullet points I've picked up in my days.&lt;/p&gt;
&lt;p&gt;What I've taken away from my readings this month are two new tools I'm going to add to my belt: #NoEstimates and &amp;quot;hypothesis over requirements&amp;quot;, two concepts explained in the Allen Holub videos I share. Individually, either might be fine, but I think the real value of these are when used together. I doubt that these practices could just be adopted willy nilly by a regular scrum team tomorrow - these describe a way for business to work, as much as they describe a way for engineering teams to work. If all the stakeholders can be aligned on this though, and if the constraints on the project allow it, there's a potential for a huge benefit here.&lt;/p&gt;
&lt;p&gt;But what is there for those of us stuck in regular Scrum teams that might not be able to adopt these right away, or ever? I've become very interested in this question, and I think I've found a few practices that could meaningfully help any engineering team. I'm certainly going to be bringing these practices up at my day job, and in future I may write on their success.&lt;/p&gt;
&lt;p&gt;Videos&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WFbvJ0dVlHk"&gt;War is Peace, Freedom is Slavery, Ignorance is Strength, Scrum is Agile - Allen Holub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=QVBlnCTu9Ms"&gt;#NoEstimates - Allen Holub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sz_LgBAGYyo"&gt;How to Build Products Users Love - Kevin Hale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=3YCIw3gewFE"&gt;Agile Software Architecture - Ian Cooper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=EZ05e7EMOLM"&gt;TDD, Where did it all go Wrong - Ian Cooper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Articles to Ponder&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@antweiss/the-true-capacity-of-your-engineering-team-38da00bd83e8"&gt;The True Capacity of Your Engineering Team - Ant Weiss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lucasfcosta.com/2021/09/20/monte-carlo-forecasts.html"&gt;How to replace estimations and guesses with a Monte Carlo simulation - Lucas F Costa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Actually, read just about anything from Lucas F Costa: &lt;a href="https://lucasfcosta.com/"&gt;lucasfcosta.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.davefarley.net/?p=278"&gt;Science and Software Development - Dave Farley&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things you can do Today&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hackerearth.com/community-hackathons/resources/e-books/guide-to-organize-hackathon/"&gt;The Complete Guide to Organizing a Successful Hackathon - Hackerearth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dylanpaulus.com/posts/start-with-no"&gt;Start With No - Dylan Paulus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taylor.town/friday-demos"&gt;Share Demos Every Friday - taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bobbiechen.com/blog/2022/7/20/being-on-call-sucks"&gt;Being On-Call Sucks - Bobbie Chen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And to close out, I wrote a few articles recently that are on-topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/reclaim_your_agile.html"&gt;Reclaim Your Agile: The One Clever Trick Agile Coaches Don't Want You to Know&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/clean_meetings_a_software_engineers_guide.html"&gt;Clean Meetings: A Software Engineer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/my_continuing_descent_into_madness.html"&gt;My (Continuing) Descent Into Madness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/a_scrum_odyssey.html"&gt;A Scrum Odyssey&lt;/a&gt; (Okay that one's a lot of GPT but it was translating my writing)&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 16 Dec 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-12-16T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_12-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_12-2024.html</link>
      <title>Book Club 12/2024: Team Metrics and KPIs</title>
      <description>&lt;p&gt;Happy holiday season and new year to you all! In the week between the holidays here I'm thinking about metrics and KPIs and the whatnot that we use in our team processes. Are they useful? My typical answer for any such question on this blog: yes of course, &lt;em&gt;if&lt;/em&gt; they're being used well for a &lt;em&gt;particular&lt;/em&gt; situation and so on and so forth. Measuring things can be good or bad.&lt;/p&gt;
&lt;p&gt;Is there a feeling that metrics are largely useless? I don't know. There's plenty of opinions to be found on good and bad metrics, and good and bad &lt;em&gt;use&lt;/em&gt; of metrics. I don't have a lot of patience to entertain a whole host of disparate, two-bit opinions, so I might suggest cutting through everything by asking: Are the metrics measuring something of value which are used to address a specific problem, and is the whole deal well-defined?&lt;/p&gt;
&lt;p&gt;Most scrum teams measuring velocity are doing so out of habit, and that's probably useless (this is where the term &amp;quot;cargo cult&amp;quot; is often misapplied in blog posts like these), but other teams might have noticed some issue in their process and solved it by measuring a KPI. Specific measurement, specific problem, specific definition. The KPI for KPIs seems to be degree of specificity. Are meta-KPIs a thing? I did a Google search after writing that last question and can happily report that they are not a thing.&lt;/p&gt;
&lt;p&gt;I've got a couple stories about metrics then, one bad and one good.&lt;/p&gt;
&lt;p&gt;The bad one relates to code coverage. This, like velocity, is one of the typically useless metrics. Not because of some inherent uselessness, but it happens more often than not that they're misused in some way, typically they're there just for the sake of it. But my story about code coverage:&lt;/p&gt;
&lt;p&gt;I now test all of my distributed services almost exclusively via integration testing of some sort. All the code of the service is a black box to the tests, which are able to cover all of the business and technical cases the service might fall into. I use &lt;a href="https://cucumber.io/docs/gherkin/"&gt;Gherkin&lt;/a&gt; so that the tests are readable to all stakeholders, which allows a &lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development"&gt;BDD process&lt;/a&gt; that greatly simplifies the process of quality assurance.&lt;/p&gt;
&lt;p&gt;Is a code coverage metric useful here? Unequivocally, no. What would be useful here is some way to measure what percentage of the business cases the tests cover. That's probably not achievable, but code coverage is useless. If I have written tests to prove that the service satisfies all of the business cases, the code itself is quite beside the point. Indeed, these services end up with very a very high degree of code coverage, but that metric gives me nothing of value except the percent of code not contributing to satisfy the business constraints. Maybe I have added some code to output to a non-critical or temporary admin report. Maybe I'm testing a new feature, or any host of things. Even having code coverage suggests the code doesn't mean to be there. Even worse, the build gates on the repositories for these services have coverage requirements. That's even more useless - now I can't check in these extra bits of code without some manual exclusion from the coverage report! Alas, there's an organizational momentum behind the damned thing and there's only so many demons you can stomp on at once.&lt;/p&gt;
&lt;p&gt;My second metrics story is, on the other hand, a story about a good use of metrics. Many years ago (how time flies, it feels like it was yesterday) I was leading a team developing a new SaaS product, and we noticed that work would go through periods of stagnation owing to PRs being open too long, and once several of them accrue bad things tend to happen. Indeed we discussed whether we needing a PR process and &lt;a href="https://ian.wold.guru/Posts/pull_requests_are_just_fine_thanks.html"&gt;decided that we did want it&lt;/a&gt;, so we sought to address the issue by measuring PR open time and asking &lt;a href="https://en.wikipedia.org/wiki/Five_whys"&gt;five whys&lt;/a&gt; to find all the root causes of this stagnation.&lt;/p&gt;
&lt;p&gt;I crafted a spreadsheet as a sort of dashboard to show open PRs, their state, how long they've been open, and the like. I was able to regularly touch base with PR authors and reviewers when we noticed some slippage. I mean this to be a success story about a metric, but I figure the real star here is the &amp;quot;five whys&amp;quot; method (or, perhaps, any method to get deeper than the surface level of a problem) because indeed the real causes of stagnation were varied and not all what you'd have expected. I suppose then the success of the metric is it allowed those conversations to happen efficiently.&lt;/p&gt;
&lt;p&gt;At any rate, in very short order we had addressed a number of underlying issues with the development process, and after just a couple of months we settled into a pace and process that allowed a phenomenal amount of code to be written over quite a bit of time. Great success!&lt;/p&gt;
&lt;p&gt;I don't write on metrics or KPIs, but I do &lt;a href="https://ian.wold.guru/Topics/processes.html"&gt;write a fair bit about process&lt;/a&gt;. Here's my reading/watching from the past month:&lt;/p&gt;
&lt;p&gt;Reading&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lucasfcosta.com/2022/08/31/engineering-metrics.html"&gt;Useful engineering metrics and why velocity is not one of them - Lucas F. Costa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://world.hey.com/dhh/the-luxury-of-working-without-metrics-02e5dbac"&gt;The Luxury of Working Without Metrics - DHH&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gojko.net/2010/10/05/bug-tracking-for-agile-teams/"&gt;Bug Tracking for Agile Teams - Gojko Adzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surfingcomplexity.blog/2023/09/03/on-productivity-metrics-management-consultants/"&gt;On productivity metrics and management consultants - Lorin Hochstein&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surfingcomplexity.blog/2024/11/23/ttr-the-out-of-control-metric/"&gt;TTR: the out-of-control metric - Lorin Hochstein&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://staysaasy.com/management/2023/12/07/accelerating-product-velocity.html"&gt;Practical Ways To Increase Product Velocity - Stay SaaSy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/tags/metrics.html"&gt;Several posts on metrics by Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Listening&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/podcasts/mastering-observability-customer-insights/"&gt;Mastering Observability: Unlocking Customer Insights with Gojko Adzic - InfoQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watching&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Md92s2jn1Uk"&gt;The BEST Developer Productivity Metrics We Have... SO FAR&amp;quot; - Dave Farley and Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=COEpO1vEBHc"&gt;How to Measure Success for Development Teams - Dave Farley&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TgdFA72crHM"&gt;Software Development in the 21st Century - Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=10qBxzX6TTI"&gt;Why KPIs and Metrics Should Not Become Targets - Bernard Marr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sQJlG_72rsQ"&gt;Measuring Software Developer Productivity??? - ThePrimeagen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 30 Dec 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-12-30T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_2-2026</guid>
      <link>https://ian.wold.guru/Posts/book_club_2-2026.html</link>
      <title>Book Club 2/2026: Constraints</title>
      <description>&lt;p&gt;Constraints, in a very broad sense, have been on my mind this last month. I have spent, as I imagine we all have, a lot of my career wrangling systems suffering from constraint problems. I mean &amp;quot;constraint&amp;quot; in the technical sense; the data or rules of the system have not been appropriately constrained. Interestingly, the age of a system doesn't seem to correlate to systems having poorly-implemented constraints; it seems to be a skill issue mostly.&lt;/p&gt;
&lt;p&gt;Constraints are a first-class tool we have for defining our systems. It might not seem the most intuitive at first: if we're told &amp;quot;the system needs to be &lt;em&gt;able&lt;/em&gt; to do X, Y, and Z,&amp;quot; the first thought will surely be how we craft our systems &lt;em&gt;for&lt;/em&gt; X, Y, and Z. However, by saying I want the system to behave &lt;em&gt;some&lt;/em&gt; way, I'm necessarily also saying I want it to &lt;em&gt;not&lt;/em&gt; behave in contradictory ways: if A, B, or C inhibit X, Y, or Z, then I have to also constrain my system to &lt;em&gt;not&lt;/em&gt; A, B, or C. Constraints aren't &lt;em&gt;enabling&lt;/em&gt; requirements, instead they are &lt;em&gt;preventing&lt;/em&gt; errors and error states.&lt;/p&gt;
&lt;p&gt;There's a lot of ways we can consider programming with constraints; &lt;a href="https://en.wikipedia.org/wiki/Constraint_programming"&gt;constraint programming&lt;/a&gt; is a topic in its own right. I'm going to list a number of areas of focus I've had this last month, starting with the more esoteric.&lt;/p&gt;
&lt;h2&gt;Constraint Logic Programming&lt;/h2&gt;
&lt;p&gt;Or CLP, this is not something with which most of us are regularly engaged. &lt;a href="https://en.wikipedia.org/wiki/Logic_programming"&gt;Logic programming&lt;/a&gt;, is its own paradigm of programming, separate to the rest, concerned with crafting programs as repositories of logical facts. A program executes in this context when a question is asked about these facts; programs are essentially interpreted by logic solving algorithms. Take this example lifted from the Wikipedia:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-prolog"&gt;is_parent(bob, sue).
is_parent(bob, john).
is_sibling(X, Y) :-
    is_parent(Z, X),
    is_parent(Z, Y).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then a question to run a program:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-prolog"&gt;?- is_sibling(sue, A).
A = john
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is already a form of programming by constraints; I'm &lt;em&gt;constraining&lt;/em&gt; the definitions of parentage and siblingness. However, we can go more constrainey with better constraint-aware algorithms to do the solving. Hence we get &lt;a href="https://en.wikipedia.org/wiki/Constraint_logic_programming"&gt;constraint logic programming&lt;/a&gt;, which is indeed much more powerful and has practical applications. If you want to learn to program with constraints, this is the holy grail: programs are (I'll wave my hand a bit here) composed entirely of constraints.&lt;/p&gt;
&lt;p&gt;Most problems - much more than you might think - can be very well-defined by their constraints; well-defined in the sense that the code I write makes sense for the problem. Remember that to programming constraints is to program &lt;em&gt;out&lt;/em&gt; error conditions; writing programs with CLP then is probably the best-class solution for a whole set of problems. Programming a user interface, though, is not necessarily one of those problems; HTTP clients are probably not best served by CLP also. That leaves CLP in a bit of a bind: if I want some other language bookending my whole system, why keep a separate CLP in the middle?&lt;/p&gt;
&lt;p&gt;Aha! Here is exactly the great practicality of logic programming: because the programs are &lt;em&gt;just&lt;/em&gt; a particular application of proof-solving algorithms, these languages can be expressed perfectly naturally as libraries within other languages. &lt;a href="https://minikanren.org/"&gt;MiniKanren&lt;/a&gt; is a lightweight DSL library with sibling implementations in most languages; C# has &lt;a href="https://github.com/naasking/uKanren.NET"&gt;uKanren.NET&lt;/a&gt;. There's plenty other libraries in most languages, and frankly it's trivially simple to implement a proof solver. &lt;a href="https://github.com/lifebeyondfife/Decider"&gt;Decider&lt;/a&gt; is an actively maintained non-Kanren system. This gives you an engine in whichever language you fancy to express deserving logic using CLP. It's too bad that this is so often overlooked; really a huge amount of logic can be more elegantly expressed with these tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/vptech/a-practical-introduction-to-constraint-programming-2037c91833ba"&gt;A practical introduction to Constraint Programming - Lorenzo Tabacchini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lpn.swi-prolog.org/lpnpage.php?pageid=online"&gt;Learn Prolog Now!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogit.michelin.io/an-introduction-to-datalog/"&gt;An Introduction to Datalog - Fabien Alberi&lt;/a&gt; (curiously, hosted by Michelin)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://minikanren.org/workshop/2020/minikanren-2020-paper5.pdf"&gt;Kanren Light - Marco Maggesi, Massimo Nocentini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=9248107"&gt;HackerNews: A year ago I asked about real-world use cases for Prolog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dstrohmaier.com/why-learn-prolog-in-2021/"&gt;Why Learn Prolog in 2021? - David Strohmaier&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=R2Aa4PivG0g"&gt;I See What You Mean - Peter Alvaro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=oa0qq75i9oc"&gt;Three Things I Wish I Knew When I Started Designing Languages - Peter Alvaro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=G_eYTctGZw8"&gt;Production Prolog - Michael Hendricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dependent Typing&lt;/h2&gt;
&lt;p&gt;This is some crazy voodoo magic. Most programmers (especially if you're reading my blog) are familiar with type theory - maybe not at the theoretical level, but you intuit that this won't compile:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;string variable = 12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt; is a type, &lt;code&gt;variable&lt;/code&gt; cannot have values that aren't &lt;code&gt;string&lt;/code&gt;s, and &lt;code&gt;12&lt;/code&gt; is not a &lt;code&gt;string&lt;/code&gt;. This is incredibly simple: a &lt;code&gt;string&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;. A more complicated type might be an array:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var ints = new int[2];
ints = [1, 2, 3];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C# allows the above, but it is a curious bit of code. Why would I specify a length of 2 and then overwrite the array with a length of 3? C# allows this because the type of &lt;code&gt;ints&lt;/code&gt; is just &lt;code&gt;int[]&lt;/code&gt;, but it seems like the author might have intended something else. Alas, C# does not allow its types to rely on literal values. This is what &lt;em&gt;dependent typing&lt;/em&gt; is: allowing types themselves to have values. In a dependently-typed language, the above would not compile, as &lt;code&gt;ints&lt;/code&gt; would be able to have type &lt;code&gt;int[2]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This can be an extremely powerful way of programming, which puts constraints directly in the type system. In the array example, I &lt;em&gt;constrain&lt;/em&gt; my array to only ever have length 2. Indeed, dependently-typed languages allow the programmer to apply any constraints that might be needed. So frequently we write programs expecting data to have certain shapes or properties or values; dependent typing allows us to write programs that make misrepresentations impossible, eliminating a whole class of errors.&lt;/p&gt;
&lt;p&gt;This knowledge has less practical application. To start, dependent typing is a fundamentally functional concept; the type checking algorithms that can validate these sorts of systems rely on functional constructs. Being a more esoteric branch in the functional tradition, then, the syntaxes that implementors are drawn to are, could I say, difficult to read. &lt;a href="https://en.wikipedia.org/wiki/Agda_(programming_language)"&gt;Agda&lt;/a&gt; will let you use any unicode character in a variable name. From the Wikipedia:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-agda"&gt;data _≤_ : ℕ → ℕ → Set where
  z≤n : {n : ℕ} → zero ≤ n
  s≤s : {n m : ℕ} → n ≤ m → suc n ≤ suc m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Studying this form is still hugely beneficial, as it teaches a mindset of loading assumptions into the &lt;em&gt;type system&lt;/em&gt; rather than runtime. This allows problems to be caught at compile time, and is the main tool for those looking to &lt;a href="https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/"&gt;make illegal states unrepresentable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://functional.works-hub.com/learn/dependent-types-explained-2e233"&gt;Dependent Types, Explained - Marty Stumpf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/background-thread/the-future-of-programming-is-dependent-types-programming-word-of-the-day-fcd5f2634878"&gt;The Future of Programming is Dependent Types — Marin Benčević&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.idris-lang.org/index.html"&gt;The Idris Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.type-driven.org.uk/edwinb/papers/impldtp.pdf"&gt;dris, a General Purpose Dependently Typed Programming Language: Design and Implementation - Edwin Brady&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.princeton.edu/%7Edpw/papers/DTDT-tr.pdf"&gt;Dynamic Typing with Dependent Types - Xinming Ou, Gang Tan, Yitzhak Mandelbaum, and David Walker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4i7KrG1Afbk"&gt;Idris: Practical Dependent Types with Practical Examples - Brian McKenna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VxINoKFm-S4"&gt;A Little Taste of Dependent Types - David Christiansen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=jgFAkmnBHwU"&gt;Dependent Types: Through The Looking Glass - Owein Reese&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=IOiZatlZtGU"&gt;Propositions as Types - Philip Wadler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_2jrmgO_Gq0"&gt;Making Dependent Types Practical - Chris Casinghino&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Refinement Typing&lt;/h2&gt;
&lt;p&gt;While fewer folks yet are looking at refinement typing, I think there is vastly more potential practical benefit here than in dependent typing. Actually, the two dovetail. I'm incredibly excited about this area. I would love to see &lt;a href="https://en.wikipedia.org/wiki/Refinement_type"&gt;refinement typing&lt;/a&gt; become the next big thing; most languages could probably implement this with some ease.&lt;/p&gt;
&lt;p&gt;While dependent types allow types to depend on &lt;em&gt;values&lt;/em&gt;, refinement typing allows types to depend on some pure constraint about them - not a value that could be some program variable, but some predicate function. Though, I might try to explain it another way: most languages anymore have pattern matching syntax. C# has a robust one:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;if (value is &amp;gt;= 0 and &amp;lt;= 100)
{
    //...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although this is a large wave of the hand, I think of refinement typing as attaching some pattern matching statement onto a base type; the resulting refined type is guaranteed that this pattern will always match the value of the variable with that type. The above example matches some &lt;code&gt;int&lt;/code&gt; with a pattern, perhaps we could imagine a C# with refinement types (I am &lt;em&gt;not&lt;/em&gt; proposing a syntax, mind you):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;int{&amp;gt;= 0 and &amp;lt;= 100} value = 12;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So long as we're not allowing runtime variables into this pattern statement, this is not a difficult task for the type checker. The power here, though, is immense: think of how many methods your code has with lots of guard clauses at the front? Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public User CreateUser(
    string username,
    int age,
    string email,
    decimal accountBalance
)
{
    if (username is null or { Length: 0 })
        throw new ArgumentException(&amp;quot;Username must be provided.&amp;quot;, nameof(username));

    if (age is &amp;lt; 18 or &amp;gt; 120)
        throw new ArgumentOutOfRangeException(nameof(age), &amp;quot;Age must be between 18 and 120.&amp;quot;);

    if (email is null or { Length: &amp;lt;= 3 })
        throw new ArgumentException(&amp;quot;A valid email address must be provided.&amp;quot;, nameof(email));

    if (accountBalance is &amp;lt; 0m)
        throw new ArgumentOutOfRangeException(nameof(accountBalance), &amp;quot;Account balance cannot be negative.&amp;quot;);

    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I could move these constraints into the types, I can eliminate the guards, catch errors at compile time, and ensure that callers are guarding data appropriately:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public User CreateUser(
    string{not null and {Length: &amp;gt; 0}} username,
    int{&amp;gt; 18 and &amp;lt; 120} age,
    string{not null and {Length: &amp;gt; 3}} email,
    decimal{&amp;gt; 0} accountBalance
)
{
    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;That's&lt;/em&gt; a pretty neat deal to me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Note these are all academic; there isn't a great into article I've found. Future post idea?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/MSR-TR-2009-147-SP1.pdf"&gt;Principles and Applications of Refinement Types - Andrew D. Gordon, Cédric Fournet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/pdf/2010.07763"&gt;Refinement Types - Ranjit Jhala, Niki Vazou&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ucsd-progsys.github.io/liquidhaskell-tutorial/"&gt;Programming with Refinement Types An Introduction to LiquidHaskell - Ranjit Jhala, Eric Seidel, Niki Vazou&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dl.acm.org/doi/epdf/10.1145/3610408"&gt;Focusing on Refinement Typing - Dimitrios J. Economou, Neel Krishnaswami, Jana Dunfield&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=OEdXcn1rx6k"&gt;An Introduction to Refinement Types - Ranjit Jhala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=oYTGXNrMEho"&gt;Pursuing Practical Refinement Types - Michael Perucca&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=pnktXb-BY54"&gt;Refinement Kinds: Type-Safe Programming with Practical Type-Level Computation  - Luís Caires, Bernardo Toninho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=KZwpQIpqbf4"&gt;Program Synthesis from Refinement Types - Nadia Polikarpova&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Ye0luMc4vGU"&gt;Liquid Haskell: Verification with Refinement Types - Niki Vazou&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 28 Feb 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-02-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_2-3-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_2-3-2025.html</link>
      <title>Book Club 2&amp;3/2025: Slack: Communication, Organization, Teams</title>
      <description>&lt;p&gt;So a bit ago I got shuffled around onto a new position, I am now the leader of the team responsible for PLPs, PDPs, and such item-related &lt;em&gt;things&lt;/em&gt; at &lt;a href="http://www.crateandbarrel.com"&gt;crateandbarrel.com&lt;/a&gt;. New team = new ideas, and being a remote worker throughout my whole career ideas about &lt;em&gt;communication&lt;/em&gt; are particularly at the fore. My firm uses Slack to communicate, and I think Teams and other tools present a similar experience.&lt;/p&gt;
&lt;p&gt;I've been reading a lot of disparate pieces about the functioning of teams, so my book club (for two months!) is a bit of a disjointed mess, but I think communication is the concept that ties it all together.&lt;/p&gt;
&lt;p&gt;There's a lot of different ways to do communication, particularly over messaging platforms, and different situations call for different sorts of communication. How to create, manage, and structure various Slack channels seems like a critical skill; identifying communication patterns, shortcomings, and requirements can make or break a project. Here's a couple articles to do with Slack &amp;quot;architecture&amp;quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/pjm/"&gt;How I’ve run major projects - Ben Kuhn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taylor.town/friday-demos"&gt;Share Demos Every Friday - taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've implemented Friday Slack demos to mixed success. I think colleagues enjoyed it but participation was spotty. When year-end reviews came around, those who participated more got more out of it. That practice is probably better for some teams and more mixed for others; definitely worth a try!&lt;/p&gt;
&lt;p&gt;No doubt different practices work for different situations, teams, individuals. I've got a number of ideas having worked remotely for so long, but I haven't published them because my remote working practices seem so ... personal? Inapplicable to others. I might do yet though; the following have given me some interesting thoughts even though I don't think they apply universally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://taylor.town/tips-for-remote-work"&gt;Tips for Remote Work - taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taylor.town/weekend-whenever"&gt;Weekend Whenever - taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.maartenballiauw.be/post/2018/09/12/how-to-become-a-remote-worker.html"&gt;How to become a remote worker - Maarten Balliauw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2018/02/18/working-remotely--4-years-in/"&gt;Working Remotely, 4 Years In - Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course there's different styles of communication depending on the scenario or situation -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2020/07/14/when-your-coworker-does-great-work-tell-their-manager/"&gt;When your coworker does great work, tell their manager - Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surfingcomplexity.blog/2022/06/03/youre-just-going-to-sit-there/"&gt;You’re just going to sit there??? - Surfing Complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surfingcomplexity.blog/2020/07/20/how-could-they-be-so-stupid/"&gt;“How could they be so stupid?” - Surfing Complexity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some writing on general team practices -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dylanamartin.com/2024/05/22/reflections-on-my-journey-at-brinc.html"&gt;Moving On: Reflections on my Journey at BRINC - Dylan Martin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2017/01/13/how-do-you-make-an-awesome-team/"&gt;How do you Make an Awesome Team? - Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lucasfcosta.com/2022/07/19/finish-what-you-start.html"&gt;How finishing what you start makes teams more productive and predictable - Lucas F. Costa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some writing on learning -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gojko.net/2009/10/15/how-to-promote-learning-in-software-teams/"&gt;How to promote learning in software teams - Gojko Adzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2016/04/30/building-expertise-at-work/"&gt;How does knowledge get locked up in people's heads? - Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/50pct/"&gt;I apparently got 50% better at my job last month - Ben Kuhn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/weekly/"&gt;My weekly review habit - Ben Kuhn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taylor.town/extinguish-all-notifications"&gt;Extinguish All Notifications - taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Last but not least, here are a few articles I've published generally on this mater:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/building_a_documentation_habit.html"&gt;Building a Documentation Habit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/principles_for_successful_teams.html"&gt;Principles for Successful Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/scrum_is_not_agile.html"&gt;Scrum is not Agile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/book_club_12-2023.html"&gt;Book Club 12/2023: Workflow, Process, and Agile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's really all I've got here; usually I use this book club as a way of developing, rounding out, and reinforcing ideas and practices that I've developed over my career, but I took a step back here to absorb. This is altogether a lighter, less set-in-stone sort of topic anywho. For those readers who live in the northern midwest US, I hope you're all enjoying seeing all the birds migrate back; I spotted my first egret of the year yesterday!&lt;/p&gt;
</description>
      <pubDate>Mon, 24 Mar 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-03-24T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_3-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_3-2024.html</link>
      <title>Book Club 3/2024: Simplicity</title>
      <description>&lt;p&gt;Everything is too complicated. Not just in software, everything! This isn't a novel concept, Teddy Roosevelt would have us believe that anything worth doing involves difficulty. I think this is a widely-adopted notion; simplicity in software seems to be a whole genre of software blogs. That makes my job here of sharing links quite easy though!&lt;/p&gt;
&lt;p&gt;To spare all of the usual writing, simplicity is software is valuable because it &lt;em&gt;makes all the things better&lt;/em&gt;. Simple code is easier to read! It's easier to maintain! Your code will perform 10x faster if it's &lt;em&gt;simple&lt;/em&gt;! Simple code is more extensible, testable, and debuggable! Then we might consider the software itself - the UI, UX, and all the user-considerations. Simple software is easier to use! Users are less likely to reach an unintended state with simple software! You'll have fewer support calls and lower burden of feature growth with simple software!&lt;/p&gt;
&lt;p&gt;These are all true, but a trap a lot of us fall into is neglecting complexity. Sure, our software &lt;em&gt;could&lt;/em&gt; be extremely simple, but it has to solve real user requirements. This is the main source of complexity. Sometimes a very simple piece of software can solve most requirements of most users mostly well. Sometimes a more complicated piece of software can solve those requirements &lt;em&gt;very&lt;/em&gt; well. We can't let ourselves want software more simple than the use case requires. I could just as easily rattle off plenty of pro-complexity platitudes: Complexity is necessary for real-world scenarios! Complexity is required to adequately address security and performance concerns! Architectures that maximize extension and innovation are necessarily complex!&lt;/p&gt;
&lt;p&gt;Both simplicity and complexity are necessary. The difference here is that the necessity of complexity always comes with that asterisk - the &lt;em&gt;kind&lt;/em&gt; and &lt;em&gt;level&lt;/em&gt; of complexity that is required is the minimal amount required to satisfy the requirements you have for your system. Any complexity too far beyond that is detrimental; all of the listed benefits of simplicity are, in fact, true.&lt;/p&gt;
&lt;p&gt;Understanding requirements is essential then. User requirements only capture so much. What are the environmental requirements? What quality attributes does the code need to support? These can introduce necessary complexity but often go overlooked. A complete understanding of the requirements of the code will allow you to develop a holistic view of the complexity that you need to introduce. This is a sort of &lt;em&gt;focused&lt;/em&gt; complexity - this is not just complexity that is &lt;em&gt;necessary&lt;/em&gt; but that is also &lt;em&gt;understood&lt;/em&gt;; you know why it's there. Unfocused complexity is surely just as bad as excessive complexity. No doubt we all have codebases that have the wrong &lt;em&gt;level&lt;/em&gt; of complexity, but ask also whether they have the wrong &lt;em&gt;kind&lt;/em&gt; of complexity. Maybe indeed the level of complexity is right but it's unfocused.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=oejXFgvAwTA"&gt;Large-Scale Architecture: The Unreasonable Effectiveness of Simplicity - Randy Shoup (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=P7CfWtR-ECk"&gt;Managing Complexity in Software - Hadi Hariri and Kevlin Henney (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=uAwJEFLJunk"&gt;Software Architecture, Team Topologies and Complexity Science - James Lewis (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=r0-GC3Y_OME"&gt;Highly Cohesive Software Design to tame Complexity - CodeOpinion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And surely &lt;a href="https://www.youtube.com/watch?v=4oky64qN5WI"&gt;Jonathan Blow&lt;/a&gt; has something to say on the topic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devdynamics.ai/blog/learn-code-complexity-understanding-code-complexity-with-ease/"&gt;Learn Code Complexity: Understanding Code Complexity with Ease - Mohit Trivedi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thoughtworks.com/insights/blog/technology-strategy/why-embracing-complexity-real-challenge-software-today"&gt;Why embracing complexity is the real challenge in software today - Ken Mugrage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itnext.io/why-simple-is-so-complex-362bc835b763"&gt;Why Simple is So Complex - Joe Crick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexbunardzic/software-complexity-essential-accidental-and-incidental-3i4d"&gt;Software Complexity: Essential, Accidental and Incidental - Alex Bunardzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://alexkondov.com/what-is-complexity/"&gt;What Is Complexity? - Alex Kondov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.developer.com/guides/simplifying-software-development/"&gt;Simplifying Software Development - Dick Wall&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two are kind of related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://taylor.town/compress-code"&gt;Clean your codebase with basic information theory - Taylor Troesh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jserd.springeropen.com/articles/10.1186/s40411-018-0060-6"&gt;Metric-centered and technology-independent architectural views for software comprehension - Luis F. Mendivelso, Kelly Garcés, and Rubby Casallas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;From Me&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I've had a couple articles recently that more or less relate to &amp;quot;simplicity&amp;quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;Just Use PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/its_better_to_be_consistently_incorrect_than_consistently_correct.html"&gt;It's Better to be Consistently Incorrect than Inconsistently Correct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/eight_maxims.html"&gt;Eight Maxims&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 25 Mar 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-03-25T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_4-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_4-2024.html</link>
      <title>Book Club 4/2024: I Don't Like ORMs</title>
      <description>&lt;p&gt;Indeed, &lt;a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping"&gt;Object-Relational Mappers&lt;/a&gt; (ORMs) are a negative thing to me. For the uninitiated, these are tools which &lt;em&gt;automagically&lt;/em&gt; provide ways of mapping data between the object (class) representations in our application software and the corresponding relational representations in databases (specifically, of course, relational databases). They present an alternative to writing SQL and deserializing the data manually. Popular ORMs include &lt;a href="https://learn.microsoft.com/en-us/aspnet/entity-framework"&gt;Entity Framework for .NET&lt;/a&gt; or &lt;a href="https://www.prisma.io/orm"&gt;Prisma for Node/TS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ORMs typically have two main components: some way to map between properties in a class and table/columns in the database, and some way to not have to write SQL in your application. In the case of Entity Framework and similar libraries for .NET, this takes the form of attributes on class properties for the former, and using LINQ to write the queries which then &amp;quot;intelligently&amp;quot; get translated to SQL at runtime and sent down the wire. It provides type safety and absolves the engineer from having to write SQL in their applications. Many take this to be a good thing.&lt;/p&gt;
&lt;p&gt;The problem which I have with ORMs is twofold, but it mainly boils down to the second. First, I perceive ORMs as polluting my objects - attributes and configuration code are annoying, sure, but I have concerns with vendor lock-in and more greatly that the query abstractions tend to hide what they're doing. Taking Entity Framework specifically, you need to be quite good at knowing what it's doing in the LINQ there to avoid any number of gotchas, which increases the incidence rate of footguns. This leads into my second and important gripe: ORMs offer less performance. Even if you're really good at them you're never going to get an overall performance benefit.&lt;/p&gt;
&lt;p&gt;The performance problem does frequently come from a misunderstanding of how the SQL abstraction of a particular ORM maps to SQL, yes, but it's also &lt;em&gt;just&lt;/em&gt; less performant to insert an abstraction over something. A lot will push back on me at this point that performance isn't everything - readability and maintainability are important, and my straw man here will feel that the type-safe abstraction of the ORM is more of both. I think this is misguided. Unless you know SQL quite well &lt;em&gt;and&lt;/em&gt; you know how your ORM generates SQL from its abstraction you're going to be setting up some potential very volatile code, so you need to read the code you wrote in this abstraction through this lens. Which seems easier then - writing and reading this abstracted code while considering the SQL translation, or just writing and reading SQL?&lt;/p&gt;
&lt;p&gt;One can claim &amp;quot;skill issue&amp;quot; here all they want, but the point is that I cannot see how using the ORM reduces any burden on me as I'm writing code. It only adds to the considerations I have to make. To be clear, I do believe you need to learn and know SQL well in order to be able to effectively use a relational database ever, whether you're using an ORM or not. For an engineer to desire to avoid learning and understanding SQL is lazy. Perhaps someone has indeed invested the time in learning it well and decided that they really would rather have the abstraction over it; this is an informed opinion which I can respect, and I'm sure such an engineer would be grateful for what they did learn from SQL (my favorite straw men are the respectable sort). I don't know that you can ultimately use any tool to make up for not understanding the fundamental working of databases and their query engines - just as in sports, it's not the tools that make one successful.&lt;/p&gt;
&lt;p&gt;So all that considered, my recommendation is to just use SQL. That, to me, has the lowest burden across the board. Since I need to learn SQL I know it, when I write or read SQL I don't have to jump through translations, and when it comes to mapping or deserializing the response from the database I have all the control I need. But wait! Doesn't that lead to so much more boilerplate code? No, and I'm disinclined to elaborate for I've never seen a proof of this and I believe the burden of proof is on the individual making this assertion. Please leave your proof in the comments! To anticipate a response, the boilerplate which I use over ADO.NET and copy/paste/modify for any particular project clocks in at under 100 lines, I and everyone else in the codebase know exactly what it does, and it outperforms your ORM.&lt;/p&gt;
&lt;p&gt;As a final note, there are a category of ORMs which assist you in writing actual SQL then handling just the mapping; they don't abstract over the SQL, which is my main objection. In .NET the most popular one by far is &lt;a href="https://github.com/DapperLib/Dapper"&gt;Dapper&lt;/a&gt;. Considering the low cost of entry to rolling my own mapping, I prefer and recommend that approach. However, there are projects or environments where Dapper can be useful, and I would recommend that everyone look to this library for these cases, or as an inspiration for what you can achieve on your own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=atABji4xqiI"&gt;I Would Never Use an ORM - Matteo Collina (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=tbfKZy7Y1pc&amp;amp;t=0s"&gt;The Only Database Abstraction You Need - CompSciGuy (2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4QN1BzxF8wM"&gt;I tried 8 different Postgres ORMs - Beyond Fireship (2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=evLx2TKTFI8"&gt;Why your DBA hates your ORM Hibernate or Entity application - Shad Sluiter (2020)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wozniak.ca/blog/2014/08/03/1/index.html?amp%3Butm_medium=referral"&gt;What ORMs have taught me: just learn SQL - Geoff Wozniak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.logrocket.com/node-js-orms-why-shouldnt-use/"&gt;Node.js ORMs: Why you shouldn’t use them - Thomas Hunter II&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I know that dev.to and Medium articles are a bit of a mixed bag, but these are well-considered with good, practical examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/harshhhdev/why-orms-arent-always-a-great-idea-41kg"&gt;Why ORMs Aren't Always a Great Idea - Harsh Singh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://betterprogramming.pub/why-orms-are-not-always-the-way-to-go-6aa578026a16"&gt;Why ORMs Are Not Always the Way To Go - Joseph Pellegrini&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 24 Apr 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-04-24T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_4-5-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_4-5-2025.html</link>
      <title>Book Club 4&amp;5/2025: Incidents and Resiliency</title>
      <description>&lt;p&gt;In March I moved to a different role at my current firm, and the thing that's changed the most for me is incidents. In the past quarter I have had to respond to more incidents than I had in the two and a half years prior. Comes with the territory sometimes, I suppose, but that puts the topic into my mind!&lt;/p&gt;
&lt;p&gt;I'm very happy to have been subscribed to Lorin Hochstein's blog &lt;a href="https://surfingcomplexity.blog/"&gt;Surfing Complexity&lt;/a&gt;, which has given me a lot of links to share with colleagues. Lorin's approach to rigidity in complex systems is to center the interactions of the components of the system as the potential, and sometimes actualized, causes of failure. This is opposed to the traditional model of there being a single &amp;quot;cause&amp;quot; or &amp;quot;actor&amp;quot; within the system from which the failure arose. Stated plainly this seems like an obvious thing to think, but turning that thinking into something &lt;em&gt;operational&lt;/em&gt; is a different thing altogether.&lt;/p&gt;
&lt;p&gt;Following up on an incident, how do we update our system to exclude failure-causing interactions? How do we do this in a way that avoids thinking about &amp;quot;root causes&amp;quot;? Most fundamentally, how do we even identify all the interactions in the first place? I figure that the complexity of a system will directly correlate with the complexity of incident-causing interactions. I also figure that complexity of any sort directly correlates with lack of understanding.&lt;/p&gt;
&lt;p&gt;So, as the complexity of the system increases, the complexities of the causes of failure increase, the ignorance of the workings of the system increases, and the inabilities to comprehend the causes of failures increase. In short, the probability that the holistic understanding of the causes of an incident cannot be had increases with time. The greater the possibility that I can't understand the full picture as to why an incident occurred, the less useful it is for me to engage in root cause analysis. In such a system, the root cause analysis will at best not fix the issue, but at worst will introduce new issues.&lt;/p&gt;
&lt;p&gt;What's to be done then? Resiliency, of course! Also easier said than done. The implication is that failure can never be ameliorated, but can only be managed. A system that better manages errors is more resilient and will function properly a greater percentage of the time. That might be an unfulfilling answer, but I'll ask you whether that maps better onto your experiences with complex systems?&lt;/p&gt;
&lt;p&gt;When we follow up from incidents, we can identify individual failures in components of our system, and we can probably identify several points of interaction failure. The question to answer is how we can update the components to succeed in the face of the errors they encountered, not how we can fix the problem. Assume you can't fix the problem, because you can't comprehend the problem. You can only control the limited view you have over the system, and you can only make that slice of the system behave &lt;em&gt;better&lt;/em&gt; in its error-laden environment.&lt;/p&gt;
&lt;p&gt;I was convinced to write about this topic for the book club this month by this recent post from Lorin : &lt;a href="https://surfingcomplexity.blog/2025/05/19/not-causal-chains-but-interactions-and-adaptations/"&gt;Not causal chains, but interactions and adaptations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Links from folks other than Lorin, in alphabetical order of their names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://overreacted.io/the-bug-o-notation/"&gt;The “Bug-O” Notation - Dan Abramov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://overreacted.io/writing-resilient-components/"&gt;Writing Resilient Components - Dan Abramov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gojko.net/2017/12/08/people-making-computers-crazy.html"&gt;People that make computers go crazy - Gojko Adzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gojko.net/2012/05/08/redefining-software-quality/"&gt;Redefining Software Quality - Gojko Adzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lucasfcosta.com/2020/09/05/not-having-problems.html"&gt;The most efficient way to solve problems: not having them - Lucas F. Costa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2016/10/21/consistency-vs-availability/"&gt;Consistency vs. Availability - Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2013/08/12/system-operations-over-seven-centuries.html"&gt;System operations over seven centuries - Martin Kleppman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2008/06/10/hermann-bondi-arrogance-of-certainty.html"&gt;Hermann Bondi: Arrogance of certainty - Martin Kleppman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2012/10/08/complexity-of-user-experience.html"&gt;The complexity of user experience - Martin Kleppman&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 31 May 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-05-31T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_5-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_5-2024.html</link>
      <title>Book Club 5/2024: SOLID</title>
      <description>&lt;p&gt;Happy Memorial Day to those in the US, I haven't had time to post a lot this month but hopefully this one is controversial enough to make up for it! &lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID&lt;/a&gt;, as I imagine you know (having subscribed to this newsletter), is a set of principles for developing software introduced a little over 20 years ago by - look at that - Uncle Bob. It's five guidelines which seek to constrain how we write software in such a way as to make it more likely that &amp;quot;clean&amp;quot;, maintainable code is written.&lt;/p&gt;
&lt;p&gt;I have some problems with each of these principles specifically, but in the general sense I can say a couple things. It's my (admittedly evidentially unsubstantiated) observation that we as an industry tend to latch on to dogmas very quickly. Various buzzwords, libraries, architectures, and patterns all have their five minutes of fame, so to speak, finding themselves the focus of momentary and ill-considered obsession. Some of these come and go very quickly (As an example, I don't know what's popular on NPM these days but ... probably that), and some of them last a bit longer (microservices). Some of them last far too long, and SOLID is one of those.&lt;/p&gt;
&lt;p&gt;By hyper-focusing on fad concepts, we are doing ourselves and an industry a disservice in several ways. Obviously, saddling future engineers with the bad ideas we didn't think through now is bad, but the more interesting aspect to me is that by focusing our individual attentions on these fads we're stealing our own time away from ourselves to develop thorough and rigorous skills in actually foundational skills. Taken to the extreme, an obsession with silver bullets blinds some among us from the understanding that there's nothing but hard work and careful consideration at the center of this industry.&lt;/p&gt;
&lt;p&gt;So I tend to avoid the fads as they come and go, and looking around it seems to me like SOLID is on the way out, but I'd like to contribute a drop in the bucket of the anti-SOLID sentiment. Really I'd like to be writing in &lt;em&gt;favor&lt;/em&gt; of diligence and developing an understanding of which tools fit which jobs, but SOLID serves as a great contrary example. I'll briefly touch on each of the five principles -&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;ingle Responsibility Principle - this causes more arguments and mangled code than it helps anything. At the surface it is absolutely correct to say that any given module should have a single purpose, but the trouble comes not just in defining what a &amp;quot;single responsibility&amp;quot; is but also defining how to define what a &amp;quot;single responsibility&amp;quot; is. Great, now I need a whole ontology of orders for definitions to resolve a PR! Should &amp;quot;single responsibility&amp;quot; mean that each iota of code is doing the bare minimum of things? Should it mean what Uncle Bob suggests, that it only has one &amp;quot;business reason&amp;quot; to change? Good luck defining that one too. And what is a module anyway - do we apply this to methods, classes, &lt;em&gt;and&lt;/em&gt; namespaces? Oh, not namespaces - why? There's another fight.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;O&lt;/strong&gt;pen-Closed Principle - This gets into the difficulties with inheritance in general, that being the complexity incurred by using it. This principle doesn't exacerbate the troubles with inheritance, but in my opinion it doesn't really do anything to constrain inheritance problems. Superficially sure - it says I shouldn't modify the base class! Okay, fair enough. In practice I don't really see that ever being an actual issue that comes up, rather that any use of inheritance adds complexity. There are limited cases that inheritance is useful when developing boilerplate logic, but it's never necessary to use. I think this principle just disappears by properly limiting or eliminating your use of inheritance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;L&lt;/strong&gt;iskov Substitution Principle - Again runs into the problems with inheritance, but this principle also applies to interfaces, which I like &lt;a href="https://ian.wold.guru/Posts/four_deeply_ingrained_csharp_cliches.html#interfaces"&gt;when used appropriately&lt;/a&gt;. I think that overall the use-case for interfaces is less than they tend to be used for - certainly so in the C# world - but when they're used I actually think the Liskov principle is a good one to keep in mind, generally. That said, when's the last time you saw some code checked in that might have actually violated this principle? A lot of the times this might come in from one interface implementation violating the not-documented contract for a particular method (i.e. &amp;quot;return null in this case to signify such and such&amp;quot;). While Liskov could be said to capture this, I don't think it does so explicitly enough, and so a better solution might be to adopt some principle which ameliorates the contract violation concern, specifically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I&lt;/strong&gt;nterface Segregation Principle - It's fair enough that a consumer of a module shouldn't be required to consider a non-relevant interface. This principle is good in that sense, but it runs afoul of being too nonspecific - we start getting arguments again. &amp;quot;This interface should really be broken into three interfaces&amp;quot; when the interface declared two methods is a frustrating conversation I've had in some form twice. Being clear, this isn't nearly as much a problem as SRP is, and this principle has its utility. My issue with it is similar to Liskov, I think there are other practices out there which more appropriately constrain our manner of development with respect to interfaces. That would be a good blog for me to blog...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;D&lt;/strong&gt;ependency Inversion Principle - the way this typically gets implemented, at least common to C# codebases, really grinds my gears. I wonder if this one ends up being misunderstood a lot, but I can't really say, it does seem to imply that every class should have an interface. This is a ridiculous notion, as I've discussed previously and in my article I linked above for Liskov. Utility classes with few methods and only one implementation definitely don't need an interface, and anything with only one implementation doesn't. An often-used objection here is that I might want an interface if I ever need to refactor by creating a parallel implementation, but the response is obvious: create the interface at that point in time. Maybe this was more difficult back in the day, but with modern development tools it's not difficult to do that.&lt;/p&gt;
&lt;p&gt;Upon reflection, it's quite funny how SOLID is one bad argument-generating principle followed by four milquetoast suggestions regarding interfaces. It seems like SOLID might do well to be replaced by a set of principles on the appropriate handling of abstractions and interfaces, but to my previous point I think it's a disservice to focus on these catchy - for lack of a better word - memes. Properly, it would be better for the industry to discuss principles in an &lt;code&gt;if-this-then-that&lt;/code&gt; format: given situation X, principles [A1, A2, ..., An] are beneficial because [R1, ... Rn]. I hope you all concur with me that the use of quasi-mathematical notation immediately makes my point more rigorous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reading and Watching&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;David Bryant Copeland has a whole series on SOLID: &lt;a href="https://naildrivin5.com/blog/2019/11/11/solid-is-not-solid-rexamining-the-single-responsibility-principle.html"&gt;SOLID Is Not Solid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117"&gt;Inheritance Is Evil. Stop Using It. - Nicolò Pignatelli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/news/2021/11/solid-modern-microservices/#:%7E:text=According%20to%20Orner%2C%20while%20the%20practice%20of%20software,to%20functional%20programming%20and%20microservices%20architecture%2C%20with%20examples."&gt;Is SOLID Still Relevant in Modern Software Architecture? - Vasco Veloso&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TT_RLWmIsbY"&gt;Solid Programming - No Thanks - The Primeagen&lt;/a&gt; - this just happened to come out this month&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=7YpFGkG-u1w"&gt;Where Does Bad Code Come From - Casey Muratori&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ETdulc1xk04"&gt;Ranking the SOLID Principles - Nick Chapsas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 29 May 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-05-29T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_6-7-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_6-7-2024.html</link>
      <title>Book Club 6&amp;7/2024: Postgres</title>
      <description>&lt;p&gt;I hope you all in the northern hemisphere are enjoying summer; I've been trying to take some extra time here and there to enjoy things before Minnesota inevitably plunges back into the depths of winter, so I've been writing a bit less (and combining two months' book clubs) but I think that's okay, all things considered!&lt;/p&gt;
&lt;p&gt;A few months ago I wrote a short piece recommending that we &lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;just use PostgreSQL&lt;/a&gt;, and I've spent several months becoming more entrenched in this position. This might be me jumping on the bandwagon - Postgres has become increasingly popular in recent years - but its genuine, proven capabilities leave me wondering what else could be the best de facto choice? You might argue that a default database option isn't needed, though I see a lot of value in it; at least as a standards bearer, like Git is anymore for version control, it provides a baseline for the whole industry.&lt;/p&gt;
&lt;p&gt;I've become very serious about insisting that any new projects &lt;em&gt;must&lt;/em&gt; use Postgres unless there's a clear argument in favor of another database, and that's a difficult hill to climb out of. The vast majority of professional applications are not just adequately served by a Postgres database, rather Postgres is typically one of the better options. The kicker though is that Postgres usually allows you to do a lot more than just data storage, keeping the solution architecture very minimal for a whole host of apps.&lt;/p&gt;
&lt;p&gt;I didn't touch on the ancillary capabilities that Postgres has in my original article, but from the articles below we can collate a heck of a lot. It can be your cache, message queue, job queue, or cron daemon. It can act as a document database, graph database, geospatial database, or search database. It supports any data model and easily handles complicated data patterns like event sourcing. Logging, metrics, and analytics can all live within Postgres; &lt;a href="https://www.timescale.com/"&gt;Timescale&lt;/a&gt; allows you to make Postgres your data warehouse.&lt;/p&gt;
&lt;p&gt;Most importantly, Postgres is &lt;em&gt;used&lt;/em&gt;, and it's used &lt;em&gt;a lot&lt;/em&gt;. All of these solutions are first-class and have been in use in major production systems for quite a while. There's great documentation all over for Postgres and all of these capabilities.&lt;/p&gt;
&lt;p&gt;The other month I used Redis for a project, as SignalR doesn't have the ability to use PostgreSQL as a backplane, but I'm going to see about writing that. I've been exploring using Postgres for most of the uses above, and I'm feeling quite settled that Postgres is the standard bearer.&lt;/p&gt;
&lt;p&gt;Next time you get the inkling to use something other than Postgres on a professional project, ask &lt;em&gt;why not Postgres?&lt;/em&gt; Insist on a proper, comprehensive answer that accounts for the flexibility, security, simplicity, documentation, and resilience that you get with Postgres. Yes, it's a tall hill to climb; yes, our professional work requires it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.timescale.com/blog/postgres-for-everything/"&gt;Why PostgreSQL Is the Bedrock for the Future of Data - Ajay Kulkarni&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazingcto.com/postgres-for-everything/"&gt;Just Use Postgres for Everything - Stephan Schmidt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/data-engineering-chariot/why-not-just-use-postgres-cc13a506a9b5"&gt;Why Not Just Use Postgres? - Keith Gregory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dagster.io/blog/skip-kafka-use-postgres-message-queue"&gt;Postgres: a Better Message Queue than Kafka? - Pete Hunt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://worlds-slowest.dev/posts/postgresql-message-queue/"&gt;How to use PostgreSQL like a message queue - Calvin Furano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fatdba.com/2021/07/30/pg_cron-probably-the-best-way-to-schedule-jobs-within-postgresql-database/"&gt;pg_cron : Probably the best way to schedule jobs within PostgreSQL database - FatDBA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinheinz.dev/blog/105"&gt;You Don't Need a Dedicated Cache Service - PostgreSQL as a Cache - Martin Heinz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dbvis.com/thetable/postgresql-full-text-search-the-definitive-guide/"&gt;PostgreSQL Full Text Search: The Definitive Guide - Antonello Zanini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dylanpaulus.com/posts/postgres-is-a-graph-database"&gt;Postgres: The Graph Database You Didn't Know You Had - Dylan Paulus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/architecting-petabyte-scale-analytics-by-scaling-out-postgres-on/ba-p/969685"&gt;Architecting petabyte-scale analytics by scaling out Postgres on Azure with the Citus extension - Claire Giordano&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some videos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nWchov5Do-o"&gt;The Only Database Abstraction You Need - The Primagen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=pfL-9ntZqnI"&gt;Using PostgreSQL to Handle Calendar Data Like a Freak - Rob Conery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=k5PQq9a4YqA"&gt;Just in time compilation in PostgreSQL - Andres Freund&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And now for something completely different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=0rlATWBNvMw"&gt;DHH discusses SQLite (and Stoicism) - Aaron Francis with DHH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 29 Jul 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-07-29T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_6-7-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_6-7-2025.html</link>
      <title>Book Club 6&amp;7/2025: OOP</title>
      <description>&lt;p&gt;Happy summer! It's been a couple months since I checked in on my newsletter; I've been busy enjoying sweltering heat, lots of rain, and wildfire smoke. In truth, I've been focusing more on a new role at work than my blog, but I promise I have found plenty of gripes there that I will resume my regular posting cadence here shortly! These last two months though, I've been thinking about object-orientation, so today you get some ramblings at a high level.&lt;/p&gt;
&lt;p&gt;As ideas ebb and flow in popularity in the software world, OOP is perhaps a bit on the ebb nowadays. Certainly it's not difficult to understand a contemporary distaste with OOP. At the risk of gross oversimplification, Java and C# engineers (and others I'm sure) have over the last several decades developed a style of programming which emphasizes overengineering and overarchitecting, developing massive codebases of passthrough layers, pointless interfaces, useless abstractions over sometimes useful abstractions; no doubt all reflections of the sometimes bizarre bureaucratic structures of the larger firms that typically employ these languages. This is the monster a lot of our colleagues envision when hearing &amp;quot;object-oriented programming,&amp;quot; and they're not entirely off-base.&lt;/p&gt;
&lt;p&gt;While this is what OOP means these days - at least colloquially - any rigorous definition of OOP isn't going to make it obvious that such code necessarily results from the paradigm. Indeed, surely in a decade there will be a revival of &amp;quot;classic OOP&amp;quot; or some nonsense as the popularity pendulum overcorrects far to the other side. These &amp;quot;classic OOP&amp;quot;ers will insist that OOP is &lt;em&gt;solely&lt;/em&gt; about objects, or encapsulation+polmorphism, or &lt;em&gt;message passing&lt;/em&gt;. I feel confident in my prediction here as this sort of defense has already begun in some circles, and again it's not entirely off base. These are all suitable definitions.&lt;/p&gt;
&lt;p&gt;On that last one, message passing: if you spend a sufficient time reading about OOP you'll uncover the group of colleagues who insist that Smalltalk is the only real OOP language. Avoiding the lower-level debate about what it means for a word to mean some thing (in this case, &amp;quot;OOP&amp;quot; to definitionally include &amp;quot;Java&amp;quot;), such a claim doesn't quite pass the sniff test to anybody with a decent understanding of the history of programming languages; the OOP ideas all came through in various stages and together form a consistent thread of ideas for approaching software architecture and engineering. Nonetheless, I'd be hard-pressed to defend the idea that the present understanding of OOP is better than the more limited Smalltalk-esque understanding; the more focused principle (or, maybe, more principled focus) of the messaging notion of OOP might well provide the necessary constraints for software to be developed better. Perhaps that's why so much new software is being developed in Smalltalk these days! Well...&lt;/p&gt;
&lt;p&gt;Perhaps it's unfair to conclude from Smalltalk's failure that its particular paradigm is also destined to failure, but I wouldn't hold my breath. So too for other paradigms that claim some sort of purity; there are no silver bullets. Today's OOP has developed as a hodgepodge of many different ideas, forms, and learnings; this makes it as undefinable as unfocused, or maybe un-specific. There's sometimes many valid approaches to the same problem within the present OOP canon. Yet, it would be a mistake to conclude that this is a bug instead of a feature, for the very reason that OOP has developed in this way is precisely &lt;em&gt;because&lt;/em&gt; it has been forced to solve just about every programming task under the sun. Recognizing that different problems will require (sometimes very) different paradigms to optimally solve, we must conclude that our languages and architectures must have the flexibility to accommodate many different approaches at once, thus making the hodgepodgeness of OOP a practical necessity.&lt;/p&gt;
&lt;p&gt;So the contemporary, undefined, colloquially-understood OOP is not such a bad thing, from a certain point of view. What though when we come upon a situation that objects are insufficient bags for our logic, or that a more pure functional language compiles much more efficiently for some or another problem? Today's software is more varied and diverse than ever before, so our incidence rate of stubbing our toes on these situations is quite high now. Hence the unpopularity of OOP: the traditional languages within the paradigm have not been flexible enough for some of the most basic procedural or functional styles best-suited to many of these tasks. On the other hand, many traditionally-OO languages have been incorporating support for other paradigms to quite a great extent; anecdotally, much of the C# world (including myself) has moved on to quite functional-looking styles as that language has (not insignificantly) morphed to accommodate it.&lt;/p&gt;
&lt;p&gt;The ideal future of OOP, should we all continue to agree this is the desirable path, is maybe a sort of fading into oblivion in which its constituent ideas are considered coequal with those from the other paradigms, and our languages and runtimes support a fluid combination of whichever structures we might discover are the most efficient solutions to our problems. There's certainly plenty of momentum in this direction, but making our tooling more permissive won't stave off any potential future hype for some &amp;quot;pure&amp;quot; form of OOP or functional or what-have-you programming; nothing can replace solid principles. OOP, by any definition, isn't good or bad, nor is it any one specific thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=wo84LFzx5nI"&gt;The Big OOPs: Anatomy of a Thirty-five-year Mistake - Casey Muratori&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=0iyB0_qPvWk"&gt;Object-Oriented Programming is Good&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=QM1iUe6IofM"&gt;Object-Oriented Programming is Bad&lt;/a&gt;, and &lt;a href="https://www.youtube.com/watch?v=V6VP-2aIcSc"&gt;Object-Oriented Programming is Garbage&lt;/a&gt; by Brian Will&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=0mbrLxAT_QI"&gt;Odin creator Ginger Bill on his programming language and state of software! - Wookash Podcast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=o9pEzgHorH0"&gt;Stop Writing Classes - Jack Diederich&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=QjJaFG63Hlo"&gt;Seminar with Alan Kay on Object Oriented Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sokb6zZC-ZE"&gt;Cuis Smalltalk and the History of Computing’s Future (with Juan Vuletich) - Developer Voices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 30 Jul 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-07-30T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_8-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_8-2024.html</link>
      <title>Book Club 8/2024: Labor</title>
      <description>&lt;p&gt;Happy Labor Day to those in the US! This is the August Book Club but I'm sending it out a couple days late to coincide with Labor Day. I don't have a central thesis to focus on, but I want to take a look into how our industry is doing. This involves matters of labor, hence the connection.&lt;/p&gt;
&lt;p&gt;The industry &lt;em&gt;feels&lt;/em&gt; like it's in a very interesting state since COVID, and there's some data to support this. The overarching sentiment I get - what I think the bulk of the focus is on - is that the industry has contracted slightly after the pandemic. I think something of the sort would be inevitable, as the spending patterns that fueled the huge intake in personnel during the pandemic wouldn't have been sustainable. Another possible inevitability is the fall in demand in lower-skilled engineers - particularly with the pool of jobs contracting it seems obvious that corporations would want &amp;quot;the most bang for their buck&amp;quot; with the engineers they retain and later hire on.&lt;/p&gt;
&lt;p&gt;This will reverse again at some point. It was true that before COVID we needed to add a huge number of software engineering jobs; the demand was very high. That demand has not disappeared. Rather, it's more likely that the spending patterns of the larger corporations have changed that we see this result. That said, I have a worry here. One way that so many engineers were hired during the pandemic was the onboarding of a record number of junior engineers, and it's this group which is disproportionately laid off. Are these folks landing on their feet within the industry or are they abandoning it? I have no data here.&lt;/p&gt;
&lt;p&gt;Something else which has contracted, much to my disappointment, is conferences. I know plenty of us dislike conferences; I am not one. I have always seen a huge value in independent conferences, particularly local ones. Earlier this year &lt;a href="https://ian.wold.guru/Posts/farewell_twin_cities_code_camp.html"&gt;I lamented the end of the Twin Cities Code Camp&lt;/a&gt;, and one year ago I attended the final &lt;a href="https://thestrangeloop.com/"&gt;Strange Loop&lt;/a&gt;. There are (some) others to fill the voids left by the closure of these and other conferences, but the landscape is lesser than it was a decade ago.&lt;/p&gt;
&lt;p&gt;I'm a strong advocate for working from home and allowing flexible hours. I've worked from home for my entire career (well before the pandemic) and I've resolved that I always will. There's no doubt that working from home does reduce the amount of and opportunities for contact with others, so I also feel strongly that a healthy home work routine involves taking advantage of frequent opportunities like conferences and meetups.&lt;/p&gt;
&lt;p&gt;To continue a somewhat bleak outlook, the recent StackOverflow developer survey reports that &lt;a href="https://survey.stackoverflow.co/2024/professional-developers#3-satisfied-at-current-job"&gt;only 20% of us are &amp;quot;happy&amp;quot; at work&lt;/a&gt;. I do think this is the first time they measured this, so I'm skeptical to read into it too much, but my off-the-cuff reaction would be that this number is short of where I would hope we would all want it to be. I don't think that a 20% happy rate today portends a happy future in the industry.&lt;/p&gt;
&lt;p&gt;This is all not in service of trying to paint a narrative that the industry is in a bad way. It certainly is in some respects, but that's not new and it's not unique to our industry. There are always better days ahead if we want there to be, and the progress that the industry has made to this point is incredible. Calling out the negative aspects and working to improve those is the best way to move forward.&lt;/p&gt;
&lt;p&gt;Here's a dump of a bunch of Primeagen videos and some others:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=BSaAMQVq01E"&gt;Expecting Professionalism - Uncle Bob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VNwjNNBFRmY"&gt;80% of Developers Dislike Their Job - Primeagen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gartner.com/en/articles/the-data-is-in-return-to-office-mandates-aren-t-worth-the-talent-risks"&gt;The Data Is In: Return-to-Office Mandates Aren’t Worth the Talent Risks - Gartner&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Turns out this was &lt;a href="https://www.youtube.com/watch?v=VNwjNNBFRmY"&gt;reviewed by Primeagen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=jCrUfzOhHYY"&gt;What GenZs Think of Software Engineering - Primeagen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hamy.xyz/labs/2024-06_return-to-office-productivity"&gt;How Return to Office Policies Kill Productivity for Software Engineers - and How to Salvage It - Hamy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VpPPHDxR9aM"&gt;The software engineering industry in 2024: what changed in 2 years, why, and what is next - The Pragmatic Engineer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=NN_icAHhmik"&gt;The SECRET To Improve Developer Productivity Is... Being HAPPY? - Continuous Delivery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=iK9lR7sdsD0"&gt;The CrowdStrike Crisis Proves The Software Industry MUST CHANGE - Continuous Delivery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 02 Sep 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-09-02T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_8-9-2025</guid>
      <link>https://ian.wold.guru/Posts/book_club_8-9-2025.html</link>
      <title>Book Club 8&amp;9/2025: Async</title>
      <description>&lt;p&gt;These last two months, the interesting thing that I'm researching is asynchronous communication patterns. Last year I wrote a whole &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;series on the Fallacies of Distributed Computing&lt;/a&gt; exploring some common dos and don'ts for systems that rely on network communication. Asynchronous communication (which, I'm just going to type &amp;quot;async&amp;quot; here on out) is a common solution to reach for as the number of nodes in the distributed network increases. It solves the problem of huge latency in requests that need to chain through several services, though at the expense of system complexity and comprehension.&lt;/p&gt;
&lt;p&gt;In an ideal world, systems would be quite &amp;quot;flat&amp;quot;, with user-level requests being able to be serviced in only a single network call to the server. Plenty of systems can't accommodate this for one reason or another, a backend might need to consult other services to fulfill a request; here though, plenty of systems can be designed so that &lt;em&gt;those&lt;/em&gt; bottom-level services don't need to make blocking calls to each other to fulfill their own requirements. Commonly, there might be a &lt;em&gt;backend-for-fronend&lt;/em&gt; that owns all of the synchronous calls, making it easier to manage temporal dependencies by isolating them all to a particular area.&lt;/p&gt;
&lt;p&gt;Suppose the lower-level services &lt;em&gt;do&lt;/em&gt; need to exchange data? Alternately, suppose we end up with too many temporal dependencies, resulting in long-running calls even though we've made them architecturally understandable within a single layer? That's where we should be inclined to reach for async. I'm supposing here that we're imagining a relatively well-designed system; that the call patterns are well-considered to fulfill actual business needs.&lt;/p&gt;
&lt;p&gt;How does async work? At the top level, when transformations happen to any data or state in the system, the service doing the changing emits an event through a shared bus, which is then read by systems that care about that transformation. Those systems will store their own representations of the data they care about, and are free to service requests on the &amp;quot;hot path&amp;quot; without having to introduce blocking calls. This has several implications: data is duplicated in many points throughout the system, the data is eventually consistent, the code for each service becomes more complex, considerations need to be made to prevent data drift, and plenty more there.&lt;/p&gt;
&lt;p&gt;Given the set of problems solved by async and the implications of adopting it, there are many patterns and ideas around them. I haven't been terribly organized or disciplined in this research; this is a grab-bag of things that have caught my eye these last two months:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/messaging"&gt;Architectural approaches for messaging in multitenant solutions - Microsoft&lt;/a&gt; Surprisingly fine overview of patterns.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@jaredhatfield/lessons-in-asynchronous-messaging-patterns-pitfalls-and-best-practices-35254b3218e8"&gt;Lessons in Asynchronous Messaging: Patterns, Pitfalls, and Best Practices - Jared Hatfield&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.martinfowler.com/articles/distributed-objects-microservices.html"&gt;Microservices and the First Law of Distributed Objects - Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brandur.org/notifier"&gt;The Notifier Pattern for Applications That Use Postgres - brandur&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joeduffyblog.com/2015/11/19/asynchronous-everything/"&gt;Asynchronous Everything - Joe Duffy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/articles/async-high-perf-login-web-farms/"&gt;Asynchronous, High-Performance Login for Web Farms - Udi Dahan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/articles/orchestrating-resilience-modern-asynchronous-systems/"&gt;Orchestrating Resilience Building Modern Asynchronous Systems - Sai Pragna Etikyala and Vikranth Etikyala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/news/2025/02/dropbox-messaging-system-model/"&gt;Dropbox’s Asynchronous Platform Evolution: from Challenges to a Unified Messaging System Model - Aditya Kulkarni&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kind of related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/ServiceOrientedAmbiguity.html"&gt;Service Oriented Ambiguity - Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.michaelnygard.com/blog/2018/01/services-by-lifecycle/"&gt;Services By Lifecycle - Michael T. Nygard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.michaelnygard.com/blog/2008/05/soa-at-3.5-million-transactions-per-hour/"&gt;SOA at 3.5 Million Transactions Per Hour - Michael T. Nygard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://particular.net/videos/cqrs-user-interaction-patterns"&gt;Better CQRS through asynchronous user interaction patterns - Udi Dahan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/presentations/rest-evolution-async-operations"&gt;No REST - Architecting Real-Time Bulk Async APIs - Michael Uzquiano&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/presentations/distributed-systems-theory/"&gt;Distributed Systems Theory for Practical Engineers - Alvaro Videla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FPBkz24QkZI"&gt;Loosely coupled orchestration with messaging - Udi Dahan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=eW4JgrkwWEM"&gt;Microservices communication patterns, messaging basics, RabbitMQ | Messaging in distributed systems - DevMentors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This has all taken me down a rabbit hole about &lt;a href="https://www.asyncapi.com/"&gt;AsyncAPI&lt;/a&gt;, but that may be for another time...&lt;/p&gt;
</description>
      <pubDate>Sat, 27 Sep 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-09-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_9-2023</guid>
      <link>https://ian.wold.guru/Posts/book_club_9-2023.html</link>
      <title>Book Club 9/2023: Papers I Love</title>
      <description>&lt;p&gt;&lt;em&gt;Author's Note: this first &amp;quot;Book Club&amp;quot; post is not included in the newsletter; I hadn't yet set up the newsletter.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Last week I attended the final &lt;a href="https://thestrangeloop.com/"&gt;Strange Loop Conference&lt;/a&gt;. This conference has been very influential on my career and my academic interests. In fact, this conference began a short time before I started making money in software. Having been able to watch the talks on the YouTubes over the years, I credit it with having a significant impact on how I've approached my career as well as my studies in college, where I got bachelors' in both philosophy and computer science.&lt;/p&gt;
&lt;p&gt;However, I had never been to a Strange Loop before! It's bitersweet then that I was able to attend the final one. Perhaps unurprisingly, I attended several presentations sponsored by (or should I say &amp;quot;presented by&amp;quot;? unsure) &lt;a href="https://paperswelove.org/"&gt;Papers We Love&lt;/a&gt;. In that spirit, I'm going to share five papers here that cut across my philosophical and computer science inclinations that I have very much enjoyed over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dinhe.net/%7Earedridel/.notmine/PDFs/Parsing/SPIEWAK%2C%20Daniel%20%282010%29%20-%20Generalized%20Parser%20Combinators.pdf"&gt;Generalized Parser Combinators - Daniel Spiewak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philpapers.org/archive/FLOSIA-5.pdf"&gt;Semantic Information and the Correctness Theory of Truth - Luciano Floridi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.type-driven.org.uk/edwinb/papers/impldtp.pdf"&gt;Idris, a General Purpose Dependently Typed Programming Language: Design and Implementation - Edwin Brady&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://people.umass.edu/klement/lambda.pdf"&gt;Russell's 1903-1905 Anticiaption of the Lambda Calculus - Kevin C. Klement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ics.uci.edu/%7Efielding/pubs/dissertation/top.htm"&gt;Architectural Styles and the Design of Network-based Software Architectures - Roy Thomas Fielding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Thu, 28 Sep 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-09-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">book_club_9-2024</guid>
      <link>https://ian.wold.guru/Posts/book_club_9-2024.html</link>
      <title>Book Club 9/2024: Blogroll</title>
      <description>&lt;p&gt;As I &lt;a href="https://ian.wold.guru/Posts/why_i_have_this_blog.html"&gt;posted a couple weeks ago&lt;/a&gt;, this month is the anniversary of restarting my blog! As though trapped in a Norm MacDonald interview, I'm frequently asked by my colleagues where I get my ideas from. Thus, for today's book club I'm just going to share a list of blogs which I tune into frequently. Some of them are folks I've bumped into at a conference, others are folks who just came across my radar at some point. Some blogs haven't gotten an update in a bit, some are quite active. All of them are somehow related to software engineering.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cultureandcommunication.org/galloway/"&gt;Alexander Galloway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://holub.com/"&gt;Allen Holub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.andrej.com/"&gt;Andrej Bauer &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ariadne.space/"&gt;Ariadne Conill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benhoyt.com/"&gt;Ben Hoyt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bcantrill.dtrace.org/"&gt;Bryan Cantrill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.benkuhn.net/"&gt;Ben Kuhn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nullprogram.com/"&gt;Chris Wellons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://overreacted.io/"&gt;Dan Abramov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dannorth.net/"&gt;Dan North&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemire.me/blog/"&gt;Daniel Lemire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://world.hey.com/dhh"&gt;David Heinemeier Hansson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ericnormand.me/"&gt;Eric Normand&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/"&gt;F# for Fun and Profit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.geepawhill.org/"&gt;GeePaw Hill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gojko.net/about/"&gt;Gojko Adzic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/"&gt;Julia Evans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://khalidabuhakmeh.com/"&gt;Khalid Abuhakmeh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surfingcomplexity.blog/"&gt;Surfing Complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lambda-the-ultimate.org/"&gt;Lambda the Ultimate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.logicmatters.net/"&gt;Logic Matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.lucasfcosta.com/"&gt;Lucas F. Costa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.philosophyofinformation.net/"&gt;Luciano Floridi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.maartenballiauw.be/"&gt;Maarten Balliauw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/"&gt;Martin Kleppmann&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://matt-rickard.com/archive"&gt;Matt Rickard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mattwarren.org/"&gt;Matt Warren&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.michaelnygard.com/"&gt;Michael Nygard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulgraham.com/index.html"&gt;Paul Graham&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wadler.blogspot.com/"&gt;Philip Wadler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://renegadeotter.com/"&gt;Renegade Otter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hanselman.com/"&gt;Scott Hanselman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://newsletter.squishy.computer/"&gt;Squishy Computer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://staysaasy.com/"&gt;Stay SaaSy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taylor.town/"&gt;taylor.town&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tomcritchlow.com/writing/"&gt;Tom Critchlow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have other blogs that you like and want me to check out (even your own), don’t ever hesitate to &lt;a href="https://ian.wold.guru/connect.html"&gt;connect with me &lt;/a&gt;, or leave a comment right here 👇🏻&lt;/p&gt;
</description>
      <pubDate>Sat, 28 Sep 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-09-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">building_a_documentation_habit</guid>
      <link>https://ian.wold.guru/Posts/building_a_documentation_habit.html</link>
      <title>Building a Documentation Habit</title>
      <description>&lt;p&gt;Two weeks ago I started working with a new team at my company, and the first (real) meeting I attended with the team was a manager's attempt to start increasing the documentation the team kept. This being the manager's goal (and, being clear, I'm talking about a good manager with a good goal), this is my goal to some extent. How do we take a longstanding team and build a habit of writing documentation?&lt;/p&gt;
&lt;p&gt;Sometimes it's not really &lt;em&gt;that&lt;/em&gt; necessary to keep up large amounts of documentation. Like code, documentation needs to be kept up-to-date, but what's more dangerous than code is that documentation can lie to us if it becomes too stale; the code always tells me exactly how it's running. The most important documentation, then, is to give a history of &lt;em&gt;why&lt;/em&gt; certain decisions were made, to explain &lt;em&gt;why&lt;/em&gt; the code looks the way it does at any point. The best time to document &lt;em&gt;why&lt;/em&gt; being when the decision happens, a longstanding team just starting to compile documentation is never going to recreate this.&lt;/p&gt;
&lt;p&gt;I'm not claiming to be some documentation guru, but we should all understand it as one of the important outputs of our professional work, so it makes sense to instill a habit for writing documenation on any team. Building any habit on a team takes two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Constantly doing; and&lt;/li&gt;
&lt;li&gt;Constantly reminding&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Don't think you can call a meeting, say &amp;quot;let's all document now,&amp;quot; and expect any change. With the team having decided it wants to build a habit, someone gets appointed the habit czar and keeps an eagle eye on opportunities to exercise it. Constant reminders by the habit czar, constant doing by the whole team. That is a bit too neat and tidy though, isn't it? If the habit is too burdensome it won't be adopted, if nobody likes the czar the team will probably start doing the opposite. Habits are people processes, and it takes some compassion and empathy on the team to be able to achieve.&lt;/p&gt;
&lt;p&gt;Back to documentation, there's a couple things to insist on:&lt;/p&gt;
&lt;h2&gt;Foundational Points&lt;/h2&gt;
&lt;p&gt;The ultimate goal is simplicity at all levels so adoption becomes effortless. Any decisions along the way should reinforce these two:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reduce the burden to document&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Don't worry about &lt;em&gt;where&lt;/em&gt; or &lt;em&gt;how&lt;/em&gt; documentation is happening. The team should pick one repository for documentation to go, and that's enough decisions to start. Nobody should feel restricted about where particular bits of documentation are put, how they're formatted, or the like. If you're starting from zero, then &lt;em&gt;any&lt;/em&gt; documentation is a step up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constantly refactor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Revisit documentation you've made, visit (and revisit) documents made by colleagues. Move files around, create or delete folders, move chunks from one file to another; any opportunity to add necessary context, remove cruft, and keep the whole structure tidy is good. Remember the &lt;a href="https://deviq.com/principles/boy-scout-rule"&gt;boyscout rule&lt;/a&gt;: always leave the documentation campsite cleaner than you found it.&lt;/p&gt;
&lt;h2&gt;Practical Points&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A good search feature is necessary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because building the habit is our primary focus, organization takes a bit of a backseat. Yes, we refactor to improve the organization of the documentation, but that ends up moving the cheese a bit. Those are good things because they are the tools to reduce the barrier to adopting the habit, and that is the goal! Do not lose focus!&lt;/p&gt;
&lt;p&gt;But how do we navigate the documentation? Shouldn't we impose a directory hierarchy at least to make it simpler? We could say that dev docs go in this folder, and architectural docs go in this other folder... NO! &lt;a href="https://grugbrain.dev/"&gt;Grug reach for club&lt;/a&gt;! You're trying to &lt;a href="https://stackify.com/premature-optimization-evil/"&gt;optimize prematurely&lt;/a&gt; and that will hurt everyone.&lt;/p&gt;
&lt;p&gt;Instead, rely on a great search tool. I really hate Atlassian products, but they have produced one good thing: the search bar in Confluence. I've heard from colleagues that the search in Notion is also quite good. Rely on that for navigation! A singular, comprehensible organization pattern will emerge over time with colleagues engaging with each other over refactoring, let that develop naturally. Even once such a pattern is established, you're never going to know perfectly where everything is. Search is friend!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Document at all the levels&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Documentation that only focuses on technical details, only on 10000-foot details, or only on why details is incomplete. Maybe you need or want incomplete documentation; that's perfectly fine! If there are techincal details that have been around a while - say, tribal knowledge like &amp;quot;don't alter this sproc otherwise Bob gets really angry&amp;quot; - that's good knowledge to have. Equally, basic architectural diagrams of what systems depend on which other systems is really great to have. There's so many points of good-to-have knowledge that it's impossible to enumerate here; I would say I'm barely scraping the iceberg but this is more like looking at the iceberg!&lt;/p&gt;
&lt;p&gt;Remember that if the documentation is bunk it can be deleted later; because you're constinuously refactoring any documentation &lt;em&gt;will&lt;/em&gt; be discussed by the team and can be moved/removed. If you're unsure about the utility, put a note at the top: &amp;quot;Ian is unsure this is long-term useful but put it here because it looked really strange as he was looking at some issue.&amp;quot;&lt;/p&gt;
&lt;p&gt;Each team having different requirements in different environments, you won't know what bits of information are really the most valueable long-term but by practice. Onboarding new engineers requires lower-level documentation, while being able to communicate the cause of an issue to a VP requires higher-level documentation. Experienced practitioners will surely have developed a sixth sense about what kinds of documentation are more valuable in different sorts of contexts; this guide is for the inexperienced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get outside feedback&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Which bits are useful, which aren't? Which bits aren't going to be able to be maintained, which bits are concrete? What document structures make the information clearest, which are overloaded or burdensome or the like? Any questions about the quality of the documentation or the quality of the experience of &lt;em&gt;reading&lt;/em&gt; the documentation can be best helped by a fresh pair of eyes.&lt;/p&gt;
&lt;p&gt;I wrote a while about &lt;a href="https://ian.wold.guru/Posts/guerrila_devex_testing.html"&gt;using guerilla testing for DevEx&lt;/a&gt;, and you can do the same here for ... DocEx? Surely we can call it something better than that...&lt;/p&gt;
&lt;p&gt;Documentation for its own sake isn't terribly useful, documentation should serve a function. When we throw up documentation that &lt;em&gt;might&lt;/em&gt; be useful for such and such reason, we'll need some way to later understand what is &lt;em&gt;actually&lt;/em&gt; useful; often interactions with the world outside the team are the best determiner of that. Did this documentation help resolve an issue raised by another team? Did Bob object to being called &amp;quot;angry&amp;quot; in the other doc? (And just to note, best practice is to not blast other colleagues in docs...)&lt;/p&gt;
</description>
      <pubDate>Wed, 19 Mar 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-03-19T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">cfweaver_version_1_published</guid>
      <link>https://ian.wold.guru/Posts/cfweaver_version_1_published.html</link>
      <title>CFWeaver Version 1.0.0 Published</title>
      <description>&lt;p&gt;I've just published verion 1.0.0 of [CFWeaver, my test generation project]! This tool has helped me on a few complicated projects at work, sorting out how systems behave. In fact, while I developed it to help generate integration test scenarios, I've used it more to help me map the functionality of some legacy bits of code.&lt;/p&gt;
&lt;p&gt;You can read my previous writing &lt;a href="https://ian.wold.guru/Posts/thing_i_made_cfweaver.html"&gt;about CFWeaver&lt;/a&gt; or more generally &lt;a href="https://ian.wold.guru/Posts/book_club_1-2025.html"&gt;about this way of thinking&lt;/a&gt; that the tool supports. The latest version is &lt;a href="https://github.com/IanWold/CFWeaver/releases/tag/v1.0.0"&gt;available for download on GitHub&lt;/a&gt; now!&lt;/p&gt;
&lt;h2&gt;Variables&lt;/h2&gt;
&lt;p&gt;In this release I added the ability to define &amp;quot;variables&amp;quot; along with each step to make it easier to track certain properties of the system through to the table of results. I had previously decided on a syntax for conditions which had the condition specified after a &lt;code&gt;?&lt;/code&gt;. This turned out to be conveniently similar to the URL query string syntax, so adding variable definitions was as simple as allowing &lt;code&gt;&amp;amp;&lt;/code&gt; after this to append variables to the result.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;* Step1: Result ? some condition &amp;amp; myVar = value
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Multi-Line Results&lt;/h2&gt;
&lt;p&gt;This was a big one since it was getting tedious to append multiple results on the same line with &lt;code&gt;|&lt;/code&gt;. Take:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;* AuthenticateOnExternalSystem: Success ? DidError = no | Timeout = 500 ? auth server timed out &amp;amp; VerifyLog = auth server timeout &amp;amp; DidError = yes | NotAuthenticated = 403 ? not authenticated | OtherError ? non-timeout error from auth server &amp;amp; VerifyLog = auth server unknown error &amp;amp; DidError = yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's not great to read. Much better is to have separate lines, and I had supposed early on that I might achieve that with nested lists. However, I really don't like whitespace dependence, even for a simple modeling language that's coopted markdown. Instead, I opted to use &lt;code&gt;-&lt;/code&gt; as list bullets to distinguish a result list lien from a step line, which isn't the &lt;em&gt;greatest&lt;/em&gt; either since it requires specific knowledge, but it strikes me as the least-bad option.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;* AuthenticateOnExternalSystem
    - Success ? DidError = no
    - Timeout = 500 ? auth server timed out &amp;amp; VerifyLog = auth server timeout &amp;amp; DidError = yes
    - NotAuthenticated = 403 ? not authenticated
    - OtherError ? non-timeout error from auth server &amp;amp; VerifyLog = auth server unknown error &amp;amp; DidError = yes
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Tests&lt;/h2&gt;
&lt;p&gt;The last thing I've done in preparation for this release is I've added an integration testing apparatus. I don't have &lt;em&gt;everything&lt;/em&gt; tested since I'm still considering the best way to test HTML and markdown output, but I do have all the errror conditions tested! This was an interesting process that I haven't done for a command line app yet.&lt;/p&gt;
&lt;p&gt;Crucially, I had to set up the application to inject the console and file I/O functionality; you can &lt;a href="https://github.com/IanWold/CFWeaver/commit/80addb8ffb3822d804e14de5e2e7afd031154700"&gt;see that in this commit&lt;/a&gt;. This allows the tests to inject fakes to capture these operations and allows me to run integration tests without needing to build the application and run it in Docker or some other squirrely setup for testing.&lt;/p&gt;
</description>
      <pubDate>Sat, 12 Apr 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-04-12T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">clean_meetings_a_software_engineers_guide</guid>
      <link>https://ian.wold.guru/Posts/clean_meetings_a_software_engineers_guide.html</link>
      <title>Clean Meetings: A Software Engineer's Guide</title>
      <description>&lt;p&gt;If being in meetings all day isn't bad enough, spending more time thinking about them seems horrible. However, meetings are going to continue to be inflicted upon us, and there will come a time (perhaps more than a few) that we'll need to inflict meetings upon our colleagues in turn. Meetings should be short, concise, and mutually beneficial to everyone involved, and in order to ensure their utility it's necessary to be mindful and considerate when facilitating or participating in a meeting.&lt;/p&gt;
&lt;p&gt;I've had to hold a lot of meetings in my time. I've spent many years in the roles of engineer, team leader, and architect, and I have to conduct meetings outside of work besides. If you want to go full-on meeting nerd I recommend that you pick up a copy of Robert's Rules, but I wanted to distill my experience and thinking on the matter into an easy-to-follow checklist for the 99.99% of us that don't want to have to spend more time than necessary on one of the admittedly more onerous parts of our profession. So, here's a simple guide on making sure you're getting the most out of your meetings.&lt;/p&gt;
&lt;p&gt;Before we get started though, I want to give the single most important advice regarding meetings. If you read no further, take this away: &lt;strong&gt;Meetings are an unfortunate tool of last resort.&lt;/strong&gt; Do you actually need to have this meeting? Would an email chain suffice? How about a Slack message, thread, or channel? Uncle Bob says (paraphrasing) that we should strive to write as few comments in our code as we can, and when we do we should acknowledge it as a failure. Take a similar approach to meetings: While maybe not a failure, we should rarely be having meetings and should, let's say, acknowledge that there might be a potential for a better way of working here. (Is that appropriately diplomatic?)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note that meetings can take any level of formality between ad-hoc meetings between junior engineers and a senior-level presentation to the CEO. Some meetings require you to write some or all of these down, such as formulating and distributing an agenda, but some do not. Irrespective the level of formality and preparation, it's good to keep these points at least in mind as you conduct meetings at different levels.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Meeting Structure&lt;/h1&gt;
&lt;p&gt;The most often overlooked aspect to keeping meetings clean is the meeting structure. Meetings have a &lt;strong&gt;purpose&lt;/strong&gt; and an &lt;strong&gt;outcome&lt;/strong&gt; that should be specified in an &lt;strong&gt;agenda&lt;/strong&gt; (whether it is explicitly written or not) that is understood and agreed to by all participants. Some participants play different &lt;strong&gt;roles&lt;/strong&gt; in the meeting, and they will take different &lt;strong&gt;actions&lt;/strong&gt; (what Robert would call &amp;quot;motions&amp;quot; but I'm not going to use that language lest I might confuse those of you who use Vim BTW) during the meeting. This might seem low-level, but it's often the details that can keep a meeting productive.&lt;/p&gt;
&lt;h2&gt;Purpose and Outcome&lt;/h2&gt;
&lt;p&gt;A meeting is scheduled with a specific set of participants agreed on a &lt;strong&gt;purpose&lt;/strong&gt; and &lt;strong&gt;outcome&lt;/strong&gt;: Why is it necessary to gather this group of people and what do they need to achieve? You might need a brainstorming session, you might need to reach a decision or consensus, or you might need to share information. Whatever the case, &lt;em&gt;be clear about each of these&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;✔️ Do define a &lt;strong&gt;purpose&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✔️ Do specify an &lt;strong&gt;outcome&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✔️ Do ensure all participants understand the &lt;strong&gt;purpose&lt;/strong&gt; and &lt;strong&gt;outcome&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❌ Do not neglect this step! Don't neglect the other steps but especially not this one!&lt;/p&gt;
&lt;p&gt;❌ Do not assume all participants understand or agree with the &lt;strong&gt;purpose&lt;/strong&gt; and &lt;strong&gt;outcome&lt;/strong&gt; - reach out and be sure&lt;/p&gt;
&lt;h2&gt;Agenda&lt;/h2&gt;
&lt;p&gt;Simply put, an agenda is a set of topics to discuss at the meeting. This can be as simple as a single bullet point (i.e. &lt;code&gt;* Brainstorm a name for the new microservice&lt;/code&gt;) or a detailed breakdown of gaps in technical knowledge.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note: if your agenda is &amp;quot;Brainstorm a name for the new microservice&amp;quot; that's just a Slack thread. I've been in that exact meeting too many times and I'm here to tell you - your microservice will be replaced by 5 others and deleted in 2 years anyway, just call it &amp;quot;Craig&amp;quot; or something.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some agendas are written, some aren't, but each meeting &lt;em&gt;has an agenda&lt;/em&gt;. There are few absolute truths in the world, but I can tell you that meetings which follow the agenda are good, and meetings that don't aren't.&lt;/p&gt;
&lt;p&gt;Another often-overlooked point is that the agenda needs to be agreed on by all participants. It is not the job of the individual calling the meeting to carve the agenda in stone, it's their responsibility to make sure the agenda fulfils the purposes intended by the participants and the outcomes desired by them. Are the intents and desires of one participant contradictory with another? Sounds like multiple meetings. Maybe multiple Slack threads, but what do I know?&lt;/p&gt;
&lt;p&gt;✔️ Do be specific about the purpose and outcome in the agenda&lt;/p&gt;
&lt;p&gt;✔️ Do communicate the agenda to participants ahead of time&lt;/p&gt;
&lt;p&gt;✔️ Do ask participants to contribute to the agenda, both before and at the beginning of each meeting&lt;/p&gt;
&lt;p&gt;❌ Do not require all potential participants to attend, especially if they feel they aren't interested in the agenda&lt;/p&gt;
&lt;p&gt;❌ Do not write the agenda in one pass. Instead, write a first pass and give ownership to the team&lt;/p&gt;
&lt;h2&gt;Roles&lt;/h2&gt;
&lt;p&gt;Each meeting has roles. At the least, you need to acknowledge:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;facilitator&lt;/strong&gt; leads the meeting, ensures the agenda is followed, and facilitates discussion,&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;secretary&lt;/strong&gt; records the minutes and important decisions, and&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;participants&lt;/strong&gt; engage in the discussion, provide input, and carry out assigned action items post-meeting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✔️ Do be clear about which role is fulfilled by which participant&lt;/p&gt;
&lt;p&gt;❌ Do not assign multiple roles to a single person in larger meetings&lt;/p&gt;
&lt;h1&gt;Before the Meeting&lt;/h1&gt;
&lt;p&gt;There are a few steps to take before holding a meeting, and they can greatly help to set the meeting up for success. The most important thing before the meeting is to remember the most important advice: Is this meeting actually necessary? Abort if not.&lt;/p&gt;
&lt;h2&gt;Prepare the Agenda&lt;/h2&gt;
&lt;p&gt;I covered all the dos and don'ts in the previous section, but this is the most important step before the meeting. Write the agenda down and distribute it as early as you can. Some meetings are regular rituals, and it still helps to write the agenda for these down and distribute them. Sometimes there is requisite technical or business information for the meeting - provide these as resources for participants. If they suggest changes, make them!&lt;/p&gt;
&lt;p&gt;✔️ Do include knowledge perrequisite for the meeting in the agenda (links and short descriptions, please)&lt;/p&gt;
&lt;p&gt;✔️ Do take feedback - if necessary, send a revised agenda to participants&lt;/p&gt;
&lt;p&gt;✔️ Do include any necessary discription or goals for points needing clarification&lt;/p&gt;
&lt;p&gt;❌ Do not write a novel for the agenda - stick to bullet points&lt;/p&gt;
&lt;p&gt;Sometimes the objective of the meeting is reached while distributing the agenda. If it is: abort the meeting. Your objective is reached. As Sun Tzu says in The Art of War, &amp;quot;The wisest general is the general who never fights.&amp;quot;&lt;/p&gt;
&lt;h2&gt;Pre-Meeting Preparation&lt;/h2&gt;
&lt;p&gt;While the facilitator has the most work to understand the agenda and work towards the objective, participants need to prepare as well. Read the agenda, suggest feedback if needed, and study the prerequisite information.&lt;/p&gt;
&lt;p&gt;✔️ Do review the agenda beforehand&lt;/p&gt;
&lt;p&gt;✔️ Do suggest agenda changes. If you think you might have solved the problem or resolved the outcome of the meeting, even if only minutes before, say so and &lt;em&gt;abort the meeting&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;❌ Do not attend the meeting if you feel you don't have to. Best to ask the facilitator to drop, explaining why&lt;/p&gt;
&lt;h1&gt;During the Meeting&lt;/h1&gt;
&lt;p&gt;In a meeting, everyone should be engaged and it should be kept as short as possible. If you're not engaged, drop. I've worked at places where I was told explicitly to not do this, but I would drop anyway and never heard any complaints. Your mileage might vary. If you're facilitating a meeting, let your participants drop.&lt;/p&gt;
&lt;h2&gt;Adhrere to the Agenda&lt;/h2&gt;
&lt;p&gt;The agenda specifies the purpose and outcome of the meeting, and the topics worthy of consideration in furtherance of the objective. At the start of the meeting, the first order of business is to approve the agenda and make any last-minute changes needed by the participants. Once everyone agrees on the agenda, &lt;em&gt;stick to it&lt;/em&gt;. Topics not on the agenda are to be tabled, either for another meeting or preferrably for a Slack thread.&lt;/p&gt;
&lt;p&gt;✔️ Do ensure everyone agrees to and understands the agenda&lt;/p&gt;
&lt;p&gt;✔️ Do be clear when moving from one agenda item to the next&lt;/p&gt;
&lt;p&gt;✔️ Do cut participants or yourself off if non-agenda topics start being discussed: &amp;quot;Let's table that thought&amp;quot;&lt;/p&gt;
&lt;p&gt;✔️ Do allow for a brief period at the end of the meeting for any additional topics if it's a wide-ranging meeting, but be eager to move individual conversations to later meetings&lt;/p&gt;
&lt;p&gt;❌ Do not amend the agenda mid-meeting. It was agreed to, and if we find we're not focused that signifies that we can &lt;em&gt;abort the meeting&lt;/em&gt; - it's not too late&lt;/p&gt;
&lt;p&gt;❌ Do not &amp;quot;afterparty&amp;quot; - these are either separate conversations or separate meetings (or better yet, Slack threads)&lt;/p&gt;
&lt;h2&gt;Time Management&lt;/h2&gt;
&lt;p&gt;The meeting should have a specific amount of time. My favorite, and I am very serious when I say favorite - historical fact is that Winston Churchill would limit all meetings &lt;em&gt;during the war&lt;/em&gt; to 20 minutes. &lt;strong&gt;YOUR MICROSERVICE'S RAM CONSUMPTION IS NOT MORE IMPORTANT THAN THE ALLIES WINNING THE SECOND WORLD WAR&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I don't know how accurate that fact is but I quote it more than once a week on average and in my experience nobody ever checks me on it, so either it's true &lt;em&gt;or&lt;/em&gt; you can also use it to keep your meetings below 20 minutes. Whatever the historical accuracy, my experience has taught me that 20 minutes is enough for almost all meetings I've had to attend or conduct. Meetings that go over 20 minutes deliver exponentially less value to participants per minute of runtime. The ideal length of a meeting is the amount time it takes to compose a Slack message. Oh look at that, your meeting can be a Slack message!&lt;/p&gt;
&lt;p&gt;✔️ Do adhere to a strict time limit. Some objectives need to be reached and the meeting extended if not, but the vast majority don't. Set the expectation with everyone that you'll hold them to a timeframe and, as if by magic, the meeting will resolve in the right amount of time&lt;/p&gt;
&lt;p&gt;✔️ Do keep conversation flexible but insert yourself when it needs to move along&lt;/p&gt;
&lt;p&gt;✔️ Do set approximate time limits for top-level items&lt;/p&gt;
&lt;p&gt;❌ Do not tell participants about those time limits - just convey how long the meeting will be&lt;/p&gt;
&lt;p&gt;❌ Don't harp on participants about time, they're doing their best to navigate the meeting; it's always OK to politely interrupt with a brief reminder about time when necessary&lt;/p&gt;
&lt;h2&gt;Take Notes&lt;/h2&gt;
&lt;p&gt;Liek the heading says, take notes. This is why I enumerated &amp;quot;secretary&amp;quot; as one of the roles in the meeting. Someone other than the facilitator should be writing down notes. &amp;quot;Even for daily standup meetings?&amp;quot; you might ask. Well, you should consider whether those are necessary, but yeah you probably should be taking notes if you're having that meeting. If you counter that nobody cares about those daily standup meetings enough to take notes, &lt;em&gt;then you might consider a second time whether those meetings really are necessary!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Notes will serve as important documentation after the fact for participants about what's been discussed and agreed to. What's more is that not everyone in your company can be in every meeting, but you need to make sure that information is available to everyone in your organization. Keep minutes to allow everyone to revisit them when necessary.&lt;/p&gt;
&lt;p&gt;✔️ Do appoint a &amp;quot;secretary&amp;quot; to take notes&lt;/p&gt;
&lt;p&gt;✔️ Do share the notes with everyone during the meeting&lt;/p&gt;
&lt;p&gt;✔️ Do take note of important questions, information, and decisions&lt;/p&gt;
&lt;p&gt;❌ Do not record every word of every participant&lt;/p&gt;
&lt;p&gt;❌ In fact, do not record the actions of specific participants unless it's absolutely necessary&lt;/p&gt;
&lt;p&gt;❌ Do not let everyone amend the minutes during the meeting - have one person dedicated to this task (brainstorming sessions maybe aside)&lt;/p&gt;
&lt;h2&gt;Everyone Must Participate&lt;/h2&gt;
&lt;p&gt;If you have a participant who isn't participating, then you don't have a &amp;quot;participant&amp;quot; - you have a voyeur. I'm creeped out by voyeurs, and you probably are too. Excuse them.&lt;/p&gt;
&lt;p&gt;Everyone in a meeting should be participating, either actively listening or contributing to the present conversation. If you're facilitating and notice someone isn't participating, ask their opinion. Create an atmosphere where they're welcome to share their opinion, no matter their seniority (either rank-wise or domain-wise). Participants asking clarifying questions, no matter how basic they may be, must be encouraged.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note on the topic of conversations: if a participant gives a hostile answer to a question, call them out on it. This is difficult but it's a win-win - the participant at the receiving end of the hostility knows you have their back, and the hostile participant (and everyone else) knows this behavior isn't acceptable.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;✔️ Do encourage a diversity in opinions and voices&lt;/p&gt;
&lt;p&gt;✔️ Do ask everyone their thoughts regularly&lt;/p&gt;
&lt;p&gt;✔️ Do actively engage in the conduct of the meeting and encourage a positive atmosphere&lt;/p&gt;
&lt;p&gt;❌ Do not let participants be passive in a meeting&lt;/p&gt;
&lt;p&gt;❌ Do not overlook non-verbal cues in virtual meetings; they can be indicators of agreement, confusion, or the desire to speak&lt;/p&gt;
&lt;p&gt;❌ Do not avoid tough conversations. About that...&lt;/p&gt;
&lt;h2&gt;Engage Difficult Conversations&lt;/h2&gt;
&lt;p&gt;Meetings are called for specific purposes - sometimes to reach decisions, sometimes to explore new ideas, or any multitude of reasons that might touch on the passions of several participants. Meetings can bring up difficult, complicated, or heated conversations or even arguments. Make no mistake - this is good, and it's a good sign that a meeting was needed if this happens. A team that is passionately engaged is infinitely more preferrable than a team that always agrees with itself. There will always be a difference in opinion, and the best thing for the team(s) involved is to engage these points head-on.&lt;/p&gt;
&lt;p&gt;✔️ Do allow heated conversations and respectful arguments&lt;/p&gt;
&lt;p&gt;✔️ Do ensure the topic is stuck to during these moments, and ensure everyone maintains the overall focus of the meeting&lt;/p&gt;
&lt;p&gt;✔️ Do act to keep participants in-line when necessary&lt;/p&gt;
&lt;p&gt;✔️ Do suggest a breather if necessary. Do reprimand participants if they cross a line&lt;/p&gt;
&lt;p&gt;✔️ Do create a safe space for dissent and disagreement. Do emphasize the focus on ideas and not individuals&lt;/p&gt;
&lt;p&gt;❌ Do not shut down difficult conversations or honest arguments&lt;/p&gt;
&lt;p&gt;❌ Do not disengage if an argument comes up&lt;/p&gt;
&lt;h1&gt;After the Meeting&lt;/h1&gt;
&lt;p&gt;There's still a bit more work to do after the meeting - you're not done yet! All the more reason to respect the time of the meeting, or to try to avoid it altogether. Remember that what was discussed needs to be appropriately documented and easy to reference for participants. Remember also that not everyone in your organization was able to attend this meeting, but goodness knows when the topics discussed will impact them.&lt;/p&gt;
&lt;p&gt;I recommend keeping your meeting minutes in a single place that's easy to reference for everyone in your organization. Products like Notion, Monday, or Confluence allow you to add tags and @ members as necessary, making them searchable too.&lt;/p&gt;
&lt;h2&gt;Finalize the Minutes&lt;/h2&gt;
&lt;p&gt;After the meeting, ask all participants to spend a minute or two to add anything to the minutes that might have been missed. You asked them to refrain from adding anything to the minutes during the meeting so they could stay focused on the conversation, now that the conversation is over they can add any extra context they need.&lt;/p&gt;
&lt;p&gt;✔️ Do leave the minutes editable, at least for a period of time. Preferrably store them in a system with history tracking.&lt;/p&gt;
&lt;p&gt;✔️ Do follow up and ensure everyone agrees on the final minutes&lt;/p&gt;
&lt;p&gt;✔️ Do broadcast to your organization that your meeting is done and the minutes are available&lt;/p&gt;
&lt;p&gt;✔️ Do set a deadline for when minutes must be finalized and shared post-meeting&lt;/p&gt;
&lt;p&gt;❌ Do not fail to publish the minutes&lt;/p&gt;
&lt;p&gt;❌ Do not allow a &amp;quot;he said, she said&amp;quot; fight in the minutes after the meeting&lt;/p&gt;
&lt;h2&gt;Follow-Up Actions&lt;/h2&gt;
&lt;p&gt;A lot of meetings will result in participants being assigned specific tasks. You should either follow up with them in the appropriate timeframe, or ensure their managers do, that they've completed these tasks. It's good to update the minutes at this time to reflect that this was done, and if possible link to the result.&lt;/p&gt;
&lt;p&gt;✔️ Do ensure all participants are clear on follow-up items&lt;/p&gt;
&lt;p&gt;✔️ Do record follow-up work in the minutes&lt;/p&gt;
&lt;p&gt;❌ Do not assign follow up work after the meeting&lt;/p&gt;
&lt;h1&gt;Keep in Mind Always&lt;/h1&gt;
&lt;h2&gt;Meeting Etiquette&lt;/h2&gt;
&lt;p&gt;✔️ Do be mindful of your role, participation, and conduct during meetings&lt;/p&gt;
&lt;p&gt;✔️ Do respect everyone's time&lt;/p&gt;
&lt;p&gt;✔️ Do respect everyone's individual contributions&lt;/p&gt;
&lt;p&gt;✔️ Do adhere to virtual meeting norms, such as muting when not speaking&lt;/p&gt;
&lt;p&gt;✔️ Do leave, not attend, or &lt;em&gt;abort&lt;/em&gt; the meeting if you can&lt;/p&gt;
&lt;p&gt;❌ Do not dominate a meeting, either as a participant or a facilitator&lt;/p&gt;
&lt;p&gt;❌ Do not underestimate the impact of your physical environment in virtual meetings (e.g., background, lighting)&lt;/p&gt;
&lt;h2&gt;Regular Review&lt;/h2&gt;
&lt;p&gt;✔️ Do regularly assess the necessity and effectiveness of meetings, especially regular rituals&lt;/p&gt;
&lt;p&gt;✔️ Do be open to feedback and make adjustments as needed&lt;/p&gt;
&lt;p&gt;✔️ Do make adjustments or &lt;em&gt;abort the meeting&lt;/em&gt; if and when necessary&lt;/p&gt;
&lt;p&gt;✔️ Do periodically ask if the frequency of regular meetings is still appropriate or if adjustments are needed&lt;/p&gt;
&lt;p&gt;❌ Do not ignore patterns of unproductive meetings. If certain meetings consistently fail to achieve their objectives, it's a sign that they need to be reevaluated&lt;/p&gt;
&lt;p&gt;❌ Do not continue meetings just because they are a routine&lt;/p&gt;
&lt;h2&gt;Diverse Perspectives&lt;/h2&gt;
&lt;p&gt;✔️ Do value and seek a range of opinions and ideas, and encourage participation from everyone&lt;/p&gt;
&lt;p&gt;✔️ Be mindful of inclusive participation, especially in diverse teams&lt;/p&gt;
&lt;p&gt;❌ Do not allow the same individuals to dominate the conversation in every meeting&lt;/p&gt;
&lt;p&gt;❌ Do not dismiss ideas without proper consideration, and do not create an environment where only certain opinions are valued over others&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Yeah that's a lot of bullet points, but I have a lot of ideas on this topic. I feel strongly that meetings are very productive tools, but there's a lot of awkwardness in our industry around how any why we have meetings. Be considerate and hold your meetings with intention and purpose, drive towards your outcome, and your meetings will work for you and all of your colleagues.&lt;/p&gt;
&lt;p&gt;Above all, remember: &lt;em&gt;you probably don't need a meeting for it.&lt;/em&gt;&lt;/p&gt;
</description>
      <pubDate>Sun, 03 Dec 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-12-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">consider_a_tiling_window_manager</guid>
      <link>https://ian.wold.guru/Posts/consider_a_tiling_window_manager.html</link>
      <title>Windows Users: Consider a Tiling Window Manager</title>
      <description>&lt;p&gt;&lt;em&gt;Note of warning to the reader: the words &amp;quot;productivity&amp;quot; and &amp;quot;efficiency&amp;quot; occur in abundance in the following text.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Tiling_window_manager"&gt;Tiling window managers&lt;/a&gt;, or TWMs, are an often-hyped feature of true Linux nerddom and a permanent staple in the toolbelt of any honest around-the-clock workflow optimizer. In short, it's what the cool kids are doing (and have been doing for about 40 years). The concept isn't new - using keyboard shortcuts and clever defaults to keep windows arranged optimally on the desktop - but they're almost entirely overlooked on Windows.&lt;/p&gt;
&lt;p&gt;This is unfortunate though, because those of us working on Windows stand to benefit just as much from the TWM as our Linux-using colleagues. Though ultimately coming down to a matter of personal preference, I would suggest that it is worthwhile for Windows users to consider a TWM. The benefits of using a TWM are essentially productivity and customizability. I've been using one on Windows for about half a year now, and the control it's given me over my workflow has had a noticeable impact on my productivity. To the point of customizability, how else could I be taken seriously when I pull out my laptop at a conference &lt;em&gt;but&lt;/em&gt; by opening it to a thoroughly riced-out RGB setup that reports my CPU temperature in Kelvin?&lt;/p&gt;
&lt;h1&gt;Keyboard Shortcuts&lt;/h1&gt;
&lt;p&gt;There's no question (in my mind at least, but hopefully in yours too) that ALT+(number) is a much more efficient way to navigate than ALT+TAB+TAB+TAB+TAB+TAB+(SHIFT+TAB)+(stare for a bit)+(release ALT). I find myself switching between windows and contexts a lot, and being able to have a single keystroke take me to the same window context &lt;em&gt;every time&lt;/em&gt; has done so much to help keep me focused, and that directly feeds into productivity. My IDE is &lt;em&gt;always&lt;/em&gt; at ALT+2 no matter where I am. I can do all my communication (Slack, Discord, email, Zoom) at ALT+3. I don't really need to set anything up each time I start my machine; everything is in its place for me.&lt;/p&gt;
&lt;p&gt;Using and learning the keyboard shortcuts isn't difficult, either. I haven't really been a keyboard shortcut person until recently, and I was able to become really proficient with the shortcuts after a couple days' use. This is a case where the investment in learning the tool is so small that it offers excellently outsized returns with the productivity gain.&lt;/p&gt;
&lt;h1&gt;Workspace Navigation&lt;/h1&gt;
&lt;p&gt;A lot of the time when we're working with windows we need to context switch twice to fully be able to reorient ourselves: once to change up our window arrangement and another to switch into the next context. I find that the workspace customization offered by TWMs eliminates the middle context switch, saving me a lot of time not just in the process of switching the windows around, but reducing the mental load I need to bear. This workspace customization extends to my multiple-monitor setup. I've already set up which tools I want on which monitors, and I have a couple of simple keystrokes to switch them between monitors when I need to switch that. This extends the predictability and efficiency of my setup, and almost entirely removed any need for me to drag windows back and forth or spend extra time trying to get my windows lining up correctly.&lt;/p&gt;
&lt;p&gt;Windows 11 has the ability to sort windows into panels, but that is lightyears behind the capabilities offered here by TWMs: with the latter I can set up any windowing arrangement without needing to take the time to manually move the windows around each time. The real estate of each screen is always perfectly optimized with a TWM. Not just that the windows are all maximized by default, but that they're arranged in the specific manager that you expect them. The same way each time, and when you need to change the window arrangement you'll be changing it in consistent and predictable ways. I've already set up how I want my windows tiled in each workspace, and that gives me a framework to work in when I need to rearrange windows as my work progresses. The less I need to focus on the minutae of where my windows are and why, the more I can focus on the work I have at hand (like typing a mediocre article on tiling window managers).&lt;/p&gt;
&lt;h1&gt;Customization is Fun&lt;/h1&gt;
&lt;p&gt;Yes, having a well-customized system is also a feedback in efficiency and productivity and all the good things. However, if I'm being honest with myself, customization is a bit of flair for flair's sake. To me, that's fun. I enjoy being able to show off a super-nerdy desktop and make things flash around with a couple keystrokes. I have a &lt;a href="https://marketplace.visualstudio.com/items?itemName=Katsute.code-background"&gt;VS Code extension&lt;/a&gt; that puts space images in the background of my code, and while I might be able to make a case that having different colored code panes better helps me find my way around my code, really it's just fun to have.&lt;/p&gt;
&lt;p&gt;TWMs offer customization in all of the workspace and productivity ways, but they also offer aesthetic customization, and you can tinker pretty fine-grained with it. Yes efficiency is important but the personalized tweaks can be a huge benefit just for their own sake. I think this is an important point about TWMs, and it's something that we sometimes lack on Windows.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;If you use Windows, consider using a TWM. Try it out for a week! It's not everyone's cup of tea, but if you're open to the idea you might find that you like it. I tried it out half a year ago just to see what the hype was and I ended up loving it. The same could be true for you.&lt;/p&gt;
&lt;p&gt;Two great ones to consider are &lt;a href="https://github.com/glzr-io/glazewm"&gt;GlazeWM&lt;/a&gt; and &lt;a href="https://github.com/LGUG2Z/komorebi"&gt;Komorebi&lt;/a&gt;. A couple others which I have not tried, but look interesting, are &lt;a href="https://github.com/ZaneA/HashTWM"&gt;HashTWM&lt;/a&gt; and &lt;a href="https://github.com/FancyWM/fancywm"&gt;FancyWM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy tiling!&lt;/p&gt;
</description>
      <pubDate>Mon, 12 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-12T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">console2048</guid>
      <link>https://ian.wold.guru/Posts/console2048.html</link>
      <title>Console2048</title>
      <description>&lt;p&gt;Jumping on the bandwagon, here's a C# implementation of Console 2048. Of course, 2048 has had a few console implementations, and most better done than this, but here it is anyway, because sometimes life is a mixed bag of apples and grapes.&lt;/p&gt;
&lt;p&gt;I've got it on the GitHub &lt;a href="https://github.com/IanWold/Console2048"&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sat, 26 Apr 2014 00:00:00 Z</pubDate>
      <a10:updated>2014-04-26T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">daily_grug</guid>
      <link>https://ian.wold.guru/Posts/daily_grug.html</link>
      <title>Daily Grug</title>
      <description>&lt;p&gt;One of favorite article is &lt;a href="https://grugbrain.dev/"&gt;The Grug Brained Developer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Much wisdom, big article, hard read. Need daily small grug.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://bit.ly/daily-grug"&gt;So made API for daily grug&lt;/a&gt; and made home page.&lt;/p&gt;
</description>
      <pubDate>Thu, 26 Oct 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-10-26T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">deploying_aspdotnet_7_projects_with_railway</guid>
      <link>https://ian.wold.guru/Posts/deploying_aspdotnet_7_projects_with_railway.html</link>
      <title>Deploying ASP.NET 7 Projects with Railway</title>
      <description>&lt;p&gt;Nevermind that I haven't posted in more than 6 years, &lt;a href="https://www.railway.app"&gt;Railway&lt;/a&gt; is a startup cloud infrastructure provider that has gained a fair amount of traction for being easy to use and very cost effective to get started with. It's pretty barebones right now, but that makes it especially great for hobbyist projects. They have a free introductory tier, but then the next tier is $5/month plus a small resource usage fee. Really, their pricing is fantastic.&lt;/p&gt;
&lt;p&gt;When you deploy with Railway, they'll shove your app into a Docker container and handle the management/scaling/etc. behind the scenes. In addition, they have the ability to stand up a database for you - as of the time of this writing, you can choose PostgreSQL, MySQL, Mongo, and Redis. That said, they of course allow you to deploy any docker image or volume, so if you're willing to put in a little more work I imagine you can make any stack work for you. That all means of course that Railway probably isn't the best solution if you need control over container orchestration, but for CRUD projects and startups it seems quite promising to me.&lt;/p&gt;
&lt;p&gt;What's especially great is their integration with GitHub - it takes just a couple minutes to sign up for Railway, authenticate with GitHub, point it at a repo, and Railway takes care of the deployment from there. It has some magic to sense what kind of project your repo is and attempt to construct a build pipeline for your project right away. This doesn't work too well in .NET, but their interface is very sparse and easy to use to get up and going with it.&lt;/p&gt;
&lt;p&gt;In the remainder of this article, I'm going to be demonstrating how to get a .NET 7 app deployed with Railway. I'll start with a simple ASP.NET API, and then I'll demonstrate getting Blazor working. I'd encourage you to follow along with me - it's no cost to you (you don't even need to type in a credit card) and I think you'll be impressed with how easy it is to get a little hobby app deployed with Railway.&lt;/p&gt;
&lt;h1&gt;Setting Up&lt;/h1&gt;
&lt;p&gt;We'll just need three things to get started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An ASP.NET API&lt;/li&gt;
&lt;li&gt;A GitHub repo for that API&lt;/li&gt;
&lt;li&gt;A Railway account&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's go top to bottom there&lt;/p&gt;
&lt;h2&gt;Setting up a new .NET API&lt;/h2&gt;
&lt;p&gt;To begin with, I'll assume you have .NET installed, and you have a GitHub account. We can create a barebones API from the console:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;dotnet new web -n RailwayAspApiDemo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts us off with the following, which will output &amp;quot;Hello, World!&amp;quot; at &lt;code&gt;/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet(&amp;quot;/&amp;quot;, () =&amp;gt; &amp;quot;Hello World!&amp;quot;);

app.Run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;MyApi&lt;/code&gt; directory, we can create a new repo. I'd recommend adding the &lt;a href="https://raw.githubusercontent.com/github/gitignore/main/VisualStudio.gitignore"&gt;VS .gitignore&lt;/a&gt; first, too.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;git add .
git commit -m &amp;quot;Getting Started&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you've created a repo in GitHub, we can push it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;git remote add origin https://github.com/{username}/RailwayAspApiDemo.git
git push -u origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For reference, you can &lt;a href="https://github.com/IanWold/RailwayAspApiDemo"&gt;see this repo here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Setting up Railway&lt;/h2&gt;
&lt;p&gt;This is real simple - just go to &lt;a href="http://railway.app"&gt;railway.app&lt;/a&gt;, click login at the top, and then you can login with GitHub.&lt;/p&gt;
&lt;p&gt;That's all you need in order to set up Railway. Seriously! Of course we're going to push ahead and click that shiny New Project button though...&lt;/p&gt;
&lt;h1&gt;Deploying our first API&lt;/h1&gt;
&lt;p&gt;If we had containerized our API with Docker, Railway would have been perfectly happy for us to give it a dockerfile, and it would deploy that no problem. However, Railway also supports building and deploying .NET apps without needing to containerize them. Let's do that first, since we're trying to keep things barebones to get started.&lt;/p&gt;
&lt;h2&gt;Deploying From a GitHub Repo&lt;/h2&gt;
&lt;p&gt;One of Railway's coolest features is that you can start a project off by pointing it at a GitHub repo, and it'll automatically (ish) deploy the repo, and set up hooks to listen to any changes on &lt;code&gt;master&lt;/code&gt; and deploy then.&lt;/p&gt;
&lt;h3&gt;Configuring the Repository for Deployment&lt;/h3&gt;
&lt;p&gt;After logging in, we should be faced with a big New Project button&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-new-project.png" alt="New Project button in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Here we'll select Deploy from GitHub repo&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-new-project-select-github.png" alt="Select GitHub in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;And then we can select the repo we just pushed&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-new-project-select-repo.png" alt="Select Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;And why not try deploying right off the bat, so long as it's giving us the option?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-new-project-deploy-repo.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;We should see the deployment fail in just a few seconds.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-deploy-first-fail.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;h3&gt;Debugging the First Errors&lt;/h3&gt;
&lt;p&gt;Let's click on the deployment and inspect the deploy logs. The first thing to notice is that Railway actually did a really good job guessing what our build config should be. At the top of the logs, we can see:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt; setup      │ dotnet-sdk                                    
────────────────────────────────────────────────────────────
 install    │ dotnet restore                                
────────────────────────────────────────────────────────────
 build      │ dotnet publish --no-restore -c Release -o out 
────────────────────────────────────────────────────────────
 start      │ ./out/RailwayAspApiDemo                       
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's really spectacular! Just because we had a &lt;code&gt;.csproj&lt;/code&gt; file, it was able to fill this all out. But it's not all peaches and pringles, we've got a build error. And indeed we're able to see a failure just a few lines down:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;#10 1.426 /nix/store/832ihvqk3vxgqqs5hvcyvg6bxqybky14-dotnet-sdk-6.0.403/sdk/6.0.403/Sdks
          /Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(144,5):
          error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0.
          Either target .NET 6.0 or lower, or use a version of the .NET SDK
          that supports .NET 7.0. [/app/RailwayAspApiDemo.csproj]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Classic cloud moment - we need to know how to configure the .NET SDK version. Thankfully, &lt;a href="https://nixpacks.com/docs/providers/csharp"&gt;Railway's docs&lt;/a&gt;, though sparse, do give us exactly what we need, an environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;NIXPACKS_CSHARP_SDK_VERSION=&amp;quot;7.0&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be set on the &lt;code&gt;Variables&lt;/code&gt; tab on the UI for the service:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-variables-sdk-version.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Adding that variable should reschedule the deployment. Indeed, it works!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-deploy-second-success.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Just one thing - how do we see it? We'll need to generate a domain ourselves in the &lt;code&gt;Settings&lt;/code&gt; tab in the UI for the service:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-settings-networking.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;That will generate a slightly random &lt;code&gt;.up.railway.app&lt;/code&gt; domain for you to get started with. Of course, you can add a custom domain here if you've purchased one, but I'm going to roll with this because somehow I managed to snag &lt;a href="http://railwayaspapidemo-production.up.railway.app"&gt;railwayaspapidemo-production.up.railway.app&lt;/a&gt;. Luky me!&lt;/p&gt;
&lt;p&gt;Now we can navigate to that link and see &amp;quot;Hello, World!&amp;quot; right?&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-failed-respond.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Well, the build deployed, so let's look at our deploy logs. I imagine yours will look similar to mine:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:3000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It says it's listening on port 3000, so it seems like the app is running, but why can't we see it? That's because when Railway deploys our app, it deploys it in a Docker container and generates a port for us. That means we either need to wire our app up to listen on the port that Railway dictates, or we need to tell Railway to use our nice, pretty port 3000. Luckily, Railway allows us to do both; the port number lives in the environment variable &lt;code&gt;PORT&lt;/code&gt;, so we can either override that in Railway or consume the environment variable from our API.&lt;/p&gt;
&lt;h3&gt;Overriding Railway's Port Assignment&lt;/h3&gt;
&lt;p&gt;To override Railway's port assignment, we can just set the environment variable in the variables tab, just like how we set the &lt;code&gt;NIXPACKS_CSHARP_SDK_VERSION&lt;/code&gt; variable earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;PORT=&amp;quot;3000&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will trigger a redeploy, and then we'll cross our fingers, refresh the app, and...&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-hello-world-success.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Nice!&lt;/p&gt;
&lt;h3&gt;Using Railway's Port in our API&lt;/h3&gt;
&lt;p&gt;Alternatively, if you want to use the Railway-generated port, we can add just a bit of code to do that. Go ahead and delete the &lt;code&gt;PORT&lt;/code&gt; environment variable if you added that.&lt;/p&gt;
&lt;p&gt;We can update our &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var builder = WebApplication.CreateBuilder(args);

if (Environment.GetEnvironmentVariable(&amp;quot;PORT&amp;quot;) is not null and string environmentPort
    &amp;amp;&amp;amp; int.TryParse(environmentPort, out int port))
{
    builder.WebHost.ConfigureKestrel(o =&amp;gt; o.ListenAnyIP(port));
}

var app = builder.Build();

app.MapGet(&amp;quot;/&amp;quot;, () =&amp;gt; &amp;quot;Hello World!&amp;quot;);

app.Run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Aside: I &lt;strong&gt;hate&lt;/strong&gt; the syntax &lt;code&gt;is not null and string&lt;/code&gt; but I'm not going to complain. Too much.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Push that code to master and you should see Railway start deploying your API right away. Once that's up, you should see &amp;quot;Hello, World!&amp;quot; in the browser at your app.&lt;/p&gt;
&lt;h2&gt;Deploy From Docker or CLI&lt;/h2&gt;
&lt;p&gt;I could type out a whole section here, but honestly I would just be copying &lt;a href="https://rendle.dev/posts/deploying-to-railway-with-dotnet/"&gt;Mark Rendle's excelent explanation&lt;/a&gt;. His tutorial was quite helpful for me getting started, and I'd like to give some credit where it's due. So, if you want to containerize your app and use the dockerfile instead of Railway's build steps, or if you want to deploy using Railway's CLI, please give his article a visit!&lt;/p&gt;
&lt;h1&gt;Deploying a Blazor App&lt;/h1&gt;
&lt;p&gt;We've got our barebones API up and running, but it's missing a number of things yet. Frankly, that's a trivial example that's hiding a number of problems that still exist. I think Blazor's a good way to demonstrate these, especially since an ASP.NET-hosted Blazor WASM project requires sharing static files.&lt;/p&gt;
&lt;h2&gt;Setting up a Blazor Project&lt;/h2&gt;
&lt;p&gt;Similar to the API we created above, we can get the default Blazor project initialized with&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;dotnet new blazorwasm --hosted -n RailwayBlazorDemo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create an &amp;quot;ASP.NET-hosted&amp;quot; Blazor app, which means we'll get a separate client and server project. Go ahead and run this locally - it will spin up the server, and the server will serve you the Blazor WASM client at root:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-blazor-default.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;Go ahead and push this to a new repository (for reference you can &lt;a href="https://github.com/IanWold/RailwayBlazorDemo"&gt;see mine here&lt;/a&gt;) and create a new project in Railway, linking to this new repository.&lt;/p&gt;
&lt;p&gt;That should start a deploy like before, and just like before you'll get a failed build. Remember to set the &lt;code&gt;NIXPACKS_CSHARP_SDK_VERSION&lt;/code&gt; environment variable, and resolve the port issue however you choose. I'll choose to resolve it in my code. In order to do that, I'll edit the &lt;code&gt;/Server/Program.cs&lt;/code&gt; file with the same lines we added to the barebones API. While that deploys, we also need to generate a domain for this app like we did before. And, don't you know it, I got lucky again: &lt;a href="http://railwayblazordemo-production.up.railway.app"&gt;railwayblazordemo-production.up.railway.app&lt;/a&gt;! Neat.&lt;/p&gt;
&lt;h3&gt;Configuring Railway to Deploy the Server&lt;/h3&gt;
&lt;p&gt;At this point, we might expect it to work. However, you'll notice after building Railway attempts to start the service several times, but fails with the same message:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;/bin/bash: line 1: ./out/RailwayBlazorDemo.Client: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uh oh - we don't want to deploy the &lt;em&gt;client&lt;/em&gt;, we want to deploy the &lt;em&gt;server&lt;/em&gt;, because the server is configured to serve the client. The cause of this can be seen in the build logs like we'd expect - the automagic build figurer-outer guessed that we wanted to deploy the client:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;setup      │ dotnet-sdk_7                                  
───────────────────────────────────────────────────────────
install    │ dotnet restore                                
───────────────────────────────────────────────────────────
build      │ dotnet publish --no-restore -c Release -o out 
───────────────────────────────────────────────────────────
start      │ ./out/RailwayBlazorDemo.Client                
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This, like the other environment configuration issues in Railway, is simple to resolve. If you navigate back to the &lt;code&gt;Settings&lt;/code&gt; tab on the UI for the service, scroll down and you'll see Deploy settings, with a helpful place to override the start command. In fact you can override any of the build steps in these settings, although you'll notice that they're pretty sparse. For our needs here though, those settings are all fine, so we'll just update the start command to &lt;code&gt;./out/RailwayBlazorDemo.Server&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-settings-start-command.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;p&gt;This will trigger a rebuild, and that should succeed! Our app should now be at the address we generated earlier, right?&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-failed-404.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;h3&gt;Configuring the ContentRootPath&lt;/h3&gt;
&lt;p&gt;Well, that's interesting, because it's a different error than we got when first deploying the barebones API earlier. In that case, we got a nice error displayed with Railway's UI. This tells us that the server is up and running - of course though we verified that when the server started logging after it deployed a minute ago. Thus, we know that the problem is with the server being able to serve up the client app.&lt;/p&gt;
&lt;p&gt;What's going on here isn't entirely obvious and it relies on a bit of knowledge about Docker to be able to intuit what's going on. There are two key lines in the logs. The key line is in the deploy logs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;Content root path: /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What's going on is that the client is stored as a static file on the server, and the server needs access to that file to be able to serve it, of course. ASP calls the root directory for static files the &amp;quot;content root path&amp;quot;, and this one is a bit bunked.&lt;/p&gt;
&lt;p&gt;This has a code solution. Replace the first line of &lt;code&gt;Server/Program.cs&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var builder = WebApplication.CreateBuilder(new WebApplicationOptions() {
    Args = args,
    ContentRootPath = &amp;quot;./&amp;quot;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, &lt;code&gt;ContentRootPath&lt;/code&gt; is &lt;em&gt;supposed&lt;/em&gt; to default to its root directory, but there's some weirdness that got introduced ... &lt;em&gt;*checks notes*&lt;/em&gt; ... somewhere? Honestly, I'm not sure how this solves it - I've just debugged enough weird directory issues with Docker in my professional career that it triggered my spidey senses.&lt;/p&gt;
&lt;p&gt;Making that change and pushing to &lt;code&gt;master&lt;/code&gt; will trigger a rebuild. Then, as if by magic, our app is working at the link:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/deploy-railway-blazor-success.png" alt="Deploy Repo in Railway" /&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Despite a few bumps in the road (which, apart from the content root path issue, make sense in context) we were able to get two .NET apps deployed in no time and with no money! In future, I think we'll explore adding a database with Railway and hooking our Blazor app up to it.&lt;/p&gt;
&lt;p&gt;I think this is the sort of case where Railway really excells. If you don't have an overly complicated backend system, deploying with Railway is extremely fast, simple, and cheap. Their focus on this area significantly reduces the barrier to entry to get a hobby app out the door. And, it seems that Railway does have enough capability to scale if you do attract users - at least through the first phase or two. Because of these factors, I'll be using Railway to deploy all my hobby apps in the future here! I'm very excited to discover a cloud provider this capable at this price.&lt;/p&gt;
&lt;p&gt;Railway's limitations are very apparent though - such is the tradeoff with the simplicity they've achieved. While you certainly can deploy any container with Railway, an overly complicated backend system could potentially become more burdensome to maintain than not. Really though, I don't know where that boundary is, but I suspect it's decently high enough that even if I do take some pet projects into production properly, I can rely on Railway to be able to serve them adequately.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;You can see my GitHub repos used in this article here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/IanWold/RailwayAspApiDemo"&gt;RailwayAspApiDemo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IanWold/RailwayBlazorDemo"&gt;RailwayBlazorDemo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://docs.railway.app/"&gt;Railway's documentation&lt;/a&gt; is pretty good, but their &lt;a href="https://discord.com/invite/railway"&gt;Discord server&lt;/a&gt; is an excellent and lively place to get help when you need it.&lt;/p&gt;
</description>
      <pubDate>Tue, 05 Sep 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-09-05T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">deploying_your_prolog_api_with_docker</guid>
      <link>https://ian.wold.guru/Posts/deploying_your_prolog_api_with_docker.html</link>
      <title>Deploying Your Prolog API with Docker</title>
      <description>&lt;p&gt;&lt;a href="https://www.swi-prolog.org/"&gt;Prolog&lt;/a&gt; is truly a leader at the forefront of modern technology, and if you're anything like me then you're convinced that this langauge is the way forward for our microservice APIs. Part of our evidence for this (as though it isn't inherently obvious) is how easy it is to containerize and deploy with Docker.&lt;/p&gt;
&lt;p&gt;To demonstrate this, let's set up a simple Hello World API with SWI Prolog:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-prolog"&gt;% Import SWI modules for HTTP servers
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).

% Set handle_request as the handler for URL /
:- http_handler('/', handle_request, []).

% Respond &amp;quot;Hello, World!&amp;quot; to requests
handle_request(_Request) :-
	format('Content-type: text/plain~n~n'),
	format('Hello, World!').

% Start the server listening to localhost:Port
server(Port) :-
	http_server(http_dispatch, [port(Port)]),
    
    % Spin while waiting for the next message
	thread_get_message(_).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's a couple things to point out here. First, accepting a &lt;code&gt;Port&lt;/code&gt; variable for the &lt;code&gt;server&lt;/code&gt; predicate allows us to keep the port flexible as an environment variable, best to not hardcode that. Second, invoking the &lt;code&gt;thread_get_message&lt;/code&gt; predicate isn't necessary when we're debugging locally but it is going to be necessary when we run it in Docker to prevent the server from halting immediately.&lt;/p&gt;
&lt;p&gt;The dockerfile isn't terribly difficult at all. We can base off of &lt;code&gt;ubuntu:latest&lt;/code&gt;, install SWI Prolog, and then run the command to start the prolog interpreter with the call to the &lt;code&gt;server&lt;/code&gt; predicate:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-dockerfile"&gt;# Use an official Ubuntu runtime as a parent image
FROM ubuntu:latest

# Set the working directory
WORKDIR /usr/src/app

# Install SWI-Prolog
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y software-properties-common &amp;amp;&amp;amp; \
    apt-add-repository ppa:swi-prolog/stable &amp;amp;&amp;amp; \
    apt-get update &amp;amp;&amp;amp; \
    apt-get install -y swi-prolog

# Copy the current directory contents into the container at /usr/src/app
COPY . .

# Make port 5000 available to the world outside this container
EXPOSE 5000

# Run swipl when the container launches
CMD [&amp;quot;swipl&amp;quot;, &amp;quot;-g&amp;quot;, &amp;quot;server(5000)&amp;quot;, &amp;quot;-t&amp;quot;, &amp;quot;halt&amp;quot;, &amp;quot;server.pl&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm hardcoding &lt;code&gt;5000&lt;/code&gt; as the HTTP port here but it's just as easy to use an environment variable at this point. Note too I'm assuming the source is in &lt;code&gt;server.pl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With this we can build the image:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;docker build -t prolog-server .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And deploy:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;docker run -p 5000:5000 prolog-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it, we're off to the races! With dev ex this smooth, Prolog will concur the world. Any day now. Surely.&lt;/p&gt;
</description>
      <pubDate>Fri, 28 Jun 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-06-28T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">deploy_your_own_nocodb_on_railway</guid>
      <link>https://ian.wold.guru/Posts/deploy_your_own_nocodb_on_railway.html</link>
      <title>Deploy Your Own NocoDB on Railway</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/nocodb/nocodb"&gt;NocoDB&lt;/a&gt; is an interesting open source project I came across some weeks ago; it aims to be an OSS alternative to &lt;a href="https://www.airtable.com/"&gt;Airtable&lt;/a&gt;. Airtable is a &amp;quot;nocode&amp;quot; solution for line-of-business applications, focusing on business process automation. It's like an enterprisey &lt;a href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt; (although; IFTTT would probably want you to think of them as the enterprisey IFTTT). NocoDB is in its early stages but seems to want to occupy the same niche, but there's a couple differences that make it useful to me for other purposes.&lt;/p&gt;
&lt;p&gt;Crucially, it's built as a UI wrapper around a database of your choice. Their documentation is a bit sparse right now, but I think I'm right in saying they support Postgres, MySQL, and SQLite at the time of this writing. If you've read my work before, you know that &lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;I like Postgres&lt;/a&gt;. There's a lot of UIs you can use with Postgres, even some that have fancy visualizations like this NocoDB, but the difference with NocoDB that's impressed me is in their support for automation: by developing their software into the no/low-code niche, it becomes more useful for those cases where I &lt;em&gt;just&lt;/em&gt; need to set up some trigger here or interaction there.&lt;/p&gt;
&lt;p&gt;I've been wanting to replace all my spreadsheets with Postgres for a while now, and NocoDB might just be the UI that lets me do that. Being OSS, I can deploy it myself with my own Postgres database for free! Here I'm going to outline how that's done in &lt;a href="https://railway.app"&gt;Railway&lt;/a&gt;, my preferred cloud host. The relevant docs from NocoDB are their &lt;a href="https://docs.nocodb.com/getting-started/self-hosted/installation/docker-install/"&gt;Docker install&lt;/a&gt; and &lt;a href="https://docs.nocodb.com/getting-started/self-hosted/environment-variables"&gt;environment variables&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The setup will be quite simple: we'll just need a Postgres database and a Docker container (with a volume) for NocoDB. We'll start by creating an empty project:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-new-project.png" alt="nocodb-railway-new-project" /&gt;&lt;/p&gt;
&lt;p&gt;To which we can add a Postgres database:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-postgres.png" alt="nocodb-railway-postgres" /&gt;&lt;/p&gt;
&lt;p&gt;I was fine with the default Postgres settings, but feel free to change yours how you need.&lt;/p&gt;
&lt;p&gt;Creating the NocoDB container is easy enough; the image &lt;code&gt;nocodb/nocodb:latest&lt;/code&gt; is available from docker hub. We can create the container by selecting &lt;code&gt;Docker Image&lt;/code&gt; when we go to create the container:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-new-docker-image.png" alt="nocodb-railway-new-docker-image" /&gt;&lt;/p&gt;
&lt;p&gt;and entering the image name&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-new-nocodb.png" alt="nocodb-railway-new-nocodb" /&gt;&lt;/p&gt;
&lt;p&gt;You can deploy this now if you like, but we're going to want to attach a volume and connect it to Postgres to work. To attach the volume, right-click on the NocoDB container:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-attach-volume.png" alt="nocodb-railway-attach-volume" /&gt;&lt;/p&gt;
&lt;p&gt;And mount the volume to &lt;code&gt;/usr/app/data/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-volume-mount.png" alt="nocodb-railway-volume-mount" /&gt;&lt;/p&gt;
&lt;p&gt;In order to connect to the postgres database, we can set the &lt;code&gt;NC_DB&lt;/code&gt; variable on the NocoDB container. You can click on the container and then the Variables tab:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-variables.png" alt="nocodb-railway-variables" /&gt;&lt;/p&gt;
&lt;p&gt;Railway has support for referencing other services in its environment variables, which we can take advantage of in constructing our connection string. When I added my variable, I used the following to get a connection string in the format that matches NocoDB's docs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;pg://${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:${{Postgres.PGPORT}}?u=${{Postgres.PGUSER}}&amp;amp;p=${{Postgres.PGPASSWORD}}&amp;amp;d=${{Postgres.PGDATABASE}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/nocodb-railway-attach-variable.png" alt="nocodb-railway-attach-variable" /&gt;&lt;/p&gt;
&lt;p&gt;That's all we need now in order to deploy and check that we're successful. Once that's verified, you can &lt;a href="https://docs.railway.com/guides/public-networking"&gt;configure a public URL&lt;/a&gt; for your NocoDB container and start using it!&lt;/p&gt;
</description>
      <pubDate>Mon, 12 May 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-05-12T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">develop_effective_coding_standards</guid>
      <link>https://ian.wold.guru/Posts/develop_effective_coding_standards.html</link>
      <title>Develop Effective Coding Standards</title>
      <description>&lt;p&gt;Coding standards can be a blessing or a curse: a practical resource that helps the team consistently maintain their codebases or an overbearing cudgel of impractical formatting prescriptions. Indeed, ineffective coding standards are worse than no coding standards: they are frustrating and can exacerbate any maladies on the team. I'd go so far as to suggest that even &lt;em&gt;effective&lt;/em&gt; coding standards are not a necessity in the industry; many teams might naturally have no need for standards.&lt;/p&gt;
&lt;p&gt;There are several situations where it's beneficial to have a solid set of standards - if the team has a large number of codebases (say, microservices) in various states of disrepair, if the team needs to onboard engineers frequently (say, for intern/junior rotations), or if your team is embarking on a 1+ year refactor of a large, legacy system with too many different styles. These are all situations where good coding standards can help ensure that a whole group of people is aligned over a period of time.&lt;/p&gt;
&lt;p&gt;I'll admit failure on my part here. I have created some godawful coding standards in the past - ones which have an exclusive focus on style that explain nits for twenty pages. I hope I've learned from my younger self, that proper standards are a very different beast. I like to think that since then I've been able to establish some standards which have been quite good. If you take nothing else away from this article, remember that the practice of creating and maintaining standards is an active, full team activity. The whole team should be engaged in developing the standards, and they should be reviewed by the whole team regularly.&lt;/p&gt;
&lt;h1&gt;Less is More&lt;/h1&gt;
&lt;p&gt;Bad coding standards are meandering swamps of useless bloviation. It's not useful to anyone to have fifty pages listing out &amp;quot;Do this, not that&amp;quot; steps. I should be able to skim the standards quite quickly and come away with a solid understanding, if just a feeling, of the way this team wants its code developed.&lt;/p&gt;
&lt;p&gt;Focus on what's truly important. You'll have plenty of &amp;quot;Do this, not that&amp;quot; examples, but use those only to disambiguate where necessary. Explain key, high-level concepts. Avoid getting too far into the weeds of specifics. Your goal should be to lay a solid foundation on top of which any number of beautiful houses can be built.&lt;/p&gt;
&lt;h1&gt;Focus on &amp;quot;Why&amp;quot;&lt;/h1&gt;
&lt;p&gt;Prescribing rules for coding is generally useless. They are annoying and can get out of date quickly, so they end up ignored in the long run. They get overlooked on review because they fade into the background when the document is in active use.&lt;/p&gt;
&lt;p&gt;What's more important - 1000 times more important - is to document why your team feels a certain way. Suppose your team prefers having a strong emphasis on being able to read your code more vertically than horizontally. You might want one rule to limit the width of the document, one rule to format ternaries on multiple lines, one rule to break conjunctions in conditionals on multiple lines, etc. Rather than stating each of these individually, they can be examples in the broader context of verticality.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;## Prefer Verticality

This team prefers writing code in a way that makes it more _vertical_ than _horizontal_. Run-on lines tend to be more difficult to read given our domain and architecture. As a consequence, the team is able to read code faster when it's consistently formatted vertically. We like to limit the horizonal width to 80 characters, and break code into multiple lines in logical places. For example:

**Use multi-line ternaries**

Do not prefer:

var myThing = first &amp;gt;= 50 ? first : second;

Do prefer:

var myThing = 
    first &amp;gt;= 50
    ? first
    : second;

**Break long conditions into multiple lines**

Do not prefer:

if (thingToCheck is SomeThing someThingToCheck &amp;amp;&amp;amp; service.CheckSomeThing(someThingToCheck) &amp;amp;&amp;amp; businessConditionForNextLogic &amp;amp;&amp;amp; featureFlagConditionForNextLogic)

Do prefer:

if (
    thingToCheck is SomeThing someThingToCheck
    &amp;amp;&amp;amp; service.CheckSomeThing(someThingToCheck)
    &amp;amp;&amp;amp; businessConditionForNextLogic
    &amp;amp;&amp;amp; featureFlagConditionForNextLogic
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By explaining why, you're giving everyone an insight as to what the team feels, not just the result of their feelings. You don't need to enumerate every single rule that follows from that feeling since you've (hopefully) articulated the &lt;em&gt;why&lt;/em&gt;. It's easier to refactor these statements as your team's feelings shift since you've written &lt;em&gt;those feelings&lt;/em&gt; down - the consequences might be harder to grasp.&lt;/p&gt;
&lt;h1&gt;Continuous Learning, Continuous Improvement&lt;/h1&gt;
&lt;p&gt;I hope that you and your teammates are learning constantly, and always becoming better engineers. As your team changes, its opinions on how code should be developed will change too. This should be reflected by continuous reviews of the coding standards. First though, you need some way to capture &lt;em&gt;that&lt;/em&gt; you're learning and &lt;em&gt;what&lt;/em&gt; you've learned.&lt;/p&gt;
&lt;p&gt;Retros are the obvious candidate - a &amp;quot;What did you learn in the last two weeks?&amp;quot; question is great. It's less useful for capturing changes in your coding disposition over time though. Monthly or quarterly learning check-ins can be good for these, but whether that works for your team to adequately capture those and translate them into changes in this document are an individual question.&lt;/p&gt;
&lt;p&gt;Your team should establish some periodicity by which it reviews all of its documentation, and your coding standards should be part of that. This can be monthly, quarterly, or yearly - just what works for you. Importantly though, consider starting these off by asking everyone on the team what they learned in that time. Did they experience anything cool or interesting, out of their usual development practice? Are they excited by something new they saw they wanted to try?&lt;/p&gt;
&lt;p&gt;However you collect it, document learnings and use them to drive the review of the coding standards. This feeds back on explaining the &lt;em&gt;why&lt;/em&gt; and not the &lt;em&gt;what&lt;/em&gt; in the document itself.&lt;/p&gt;
&lt;h1&gt;Coding Standards Affect Culture&lt;/h1&gt;
&lt;p&gt;If the standards are too nitpicky, you'll start seeing too many nits in code reviews. If the standards are too prescriptive, you'll see fewer creative ideas in the codebase. If the standards don't embrace new features, libraries, or architectures, your codebase will be stuck in the past. Maybe these are actually effects that you want - perhaps you're maintaining a large COBOL platform, and modernization will halve your paycheck. Fair enough.&lt;/p&gt;
&lt;p&gt;For the rest of us, I'd venture that we want to work on exciting, collaborative teams that encourage using new ideas to continuously refine the code. If you've got effective standards that the team is actively using, these standards need to be an example of the kind of culture you want to work in.&lt;/p&gt;
&lt;p&gt;By developing the standards themselves collaboratively, this will cause the team to develop more collaboratively. By keeping standards focused on the &lt;em&gt;why&lt;/em&gt;, PR reviews and pair programming sessions will have a focus on &lt;em&gt;why&lt;/em&gt;. If your standards allow for variance within the parameters your team must work (business requirements, etc) then your team's solution space will be as wide as possible. If you review and improve your standards at intervals where you can incorporate the latest new features of your language or libraries - for example if you're a .NET team and you review standards every November after each .NET/C# release - your team too will incorporate more of the latest features.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Are coding standards necessary? No, at least not for everyone. If your team isn't going to use the standards, don't create any. If the team isn't engaged in developing the standards, then you'll probably get subpar standards. Remember, unless you've got effective standards it's better to not have any. If your team is in a pinch where they decide they need to align on coding practice, then having concise standards to document its feelings can be beneficial. Develop the standards and review them constantly from a collaborative perspective, and focus on documenting your feelings with a small number of key examples.&lt;/p&gt;
&lt;p&gt;But how do we use these standards once we've written them? Well, curiously, you might not. If you're onboarding a new member of the team or explaining yourselves to another engineer in your organization, you should break out these standards to show off how the team feels. But if your team is really aligned on development practice - as it should be after developing a set of standards - then you and your teammates won't need them, not day-to-day. Standards can't be used as a cudgel to enforce conformity, that creates resentment and disfunction on the team.&lt;/p&gt;
&lt;p&gt;Don't take this to mean they're not useful at this point - indeed it's the very process of continuously reviewing this document that keeps your team aligned and allows everyone to update and refresh their ideas on the same page. It's curious - the primary utility of the document is not the document itself or the regular use of the document, but the regular and ongoing development of the document as a team.&lt;/p&gt;
</description>
      <pubDate>Wed, 14 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-14T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">dont_retro_the_same_twice</guid>
      <link>https://ian.wold.guru/Posts/dont_retro_the_same_twice.html</link>
      <title>Don't Retro the Same Twice</title>
      <description>&lt;p&gt;Retrospective meetings are a cornerstone for most modern software engineering teams. They're the only regular practice mentioned in the agile manifesto, they've been written about ad nauseum, and many colleagues seem quite excited on the days their teams have retrospectives. Well-conducted, the retrospective can offer a lot of benefits to a team, their projects, and their organization on the whole, so everyone &lt;em&gt;really&lt;/em&gt; wants to get the retro meeting right.&lt;/p&gt;
&lt;p&gt;There's maybe a billion (I could be underselling it) formats for retro meetings, and in their care and consideration some folks have very strong opinions on the &amp;quot;right&amp;quot; structure for retro meetings. As (I hope) my writing on this blog can attest, I'm not a fan of that way of thinking about process. Retrospectives are simple - as long as there is a way to identify and discuss problems and successes then you're good to go; specific formats give some icing on the cake.&lt;/p&gt;
&lt;p&gt;That icing can be quite important at times. While the primary utility of the retro is to identify and expedite the solution to problems, there's a host of ancillary benefits that can be gained from different retrospective formats. Indeed, different retro formats are going to be better suited to expressing different benefits. Maybe your team has a project which requires something more specific out of your retrospectives, and a particular format is obviously good for your team. That's great! Just remember it might change.&lt;/p&gt;
&lt;p&gt;For most teams, one format isn't going to be clearly superior, but they can benefit - and benefit differently - from any format. So this is the advice I'll give: Don't have the same retro meeting twice in a row. This allows your team to get the benefits of all the formats, and there's a few ways to go about it. If you've got some persnickety folks on the team, you can rotate between different preferred formats to keep them all happy. You could, ahead of the meeting, briefly describe a couple of obvious challenges to Chat GPT and ask it to recommend a retro format to suit. My preferred way is to pick a format out of a hat and run with it.&lt;/p&gt;
&lt;p&gt;Whatever you choose, don't retro the same twice! No format can do &lt;em&gt;all the things&lt;/em&gt; for you and your team, and keeping the retrospectives fresh allows your team to approach their problems from different angles. For short-term problems (problems within a sprint) any format is going to be sufficient - as I said earlier all that is really needed is the ability to identify and discuss these. However, a lot of teams have long-running or more complicated problems, and the different angles approach might be better at unblocking these.&lt;/p&gt;
&lt;p&gt;For the sake of being (too) analytical about it, here are some of the ancillary benefits (beyond identifying problems, improving process, and adapting to change) that retrospectives offer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Increase collaboration between team members&lt;/li&gt;
&lt;li&gt;Opportunity for distributed teams to build culture&lt;/li&gt;
&lt;li&gt;Celebrating individual and team successes makes everyone feel good&lt;/li&gt;
&lt;li&gt;Builds trust between members&lt;/li&gt;
&lt;li&gt;Helps individual members learn from mistakes&lt;/li&gt;
&lt;li&gt;Ensures team members have ownership over process&lt;/li&gt;
&lt;li&gt;Ensures members are aligned on team goals&lt;/li&gt;
&lt;li&gt;Allows individuals to &amp;quot;vent&amp;quot; when needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then here are some common formats, and how they express different benefits. You can find endless blogs about retro formats, so this isn't an exhaustive list but an exercise to point out how different formats can yield different benefits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Four Ls&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;Loved, Loathed, Longed For, and Learned&amp;quot;. The &amp;quot;Loathed&amp;quot; column is particularly beneficial for members to see the results of mistakes and learn from them, and the &amp;quot;Learned&amp;quot; column specifically helps to stimulate collaboration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sailboat&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;Wind (what's helping us), Anchor (what's holding us back), Rocks/Icebergs (potential future blockers), Island (what's the destination)&amp;quot;. The &amp;quot;Rocks&amp;quot; and &amp;quot;Island&amp;quot; columns ensure the retro is balanced on looking to the future as much as the past, which enhances understanding and ownership over goals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mad, Sad, Glad&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Self-descriptive, I hope! This breaks down the barriers to conversation, helping to build culture, celebrate successes, and allowing individuals to vent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Six Hats&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Each member &amp;quot;wears&amp;quot; one of the hats - Facts, Emotions, Creativity, Process, Positives, Negatives - and each issue is looked at from that perspective. Forcing these perspectives to be accounted for helps to stimulate collaboration and trust between individuals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Five Whys&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For each problem, ask &amp;quot;Why?&amp;quot; five times. By hyper-focusing on root causes, this can give the team a lot of control over its process by gaining a better understanding of how problems arise from situations or facts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;STAR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Each problem is described by the Situtation that happened, the Task that was being done, the Actions that were taken, and the Result. This is a hyper-focus on the relationship between goals, actions, and results, which is beneficial for helping individuals learn from mistakes. In the right situation it can help members vent in a productive way.&lt;/p&gt;
</description>
      <pubDate>Mon, 22 Jul 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-07-22T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">dotnet-10-single-file-finally</guid>
      <link>https://ian.wold.guru/Posts/dotnet-10-single-file-finally.html</link>
      <title>.NET 10's Single File Feature is Perfect ... Almost</title>
      <description>&lt;p&gt;.NET 10 is finally introducing a feature that is some 20 years (give or take a few) too late, allowing a single CS file to be ran as a standalone program, not requiring any solution, project, or Nuget files. This is something I've been needing for some time - yes, needing, not wanting - so I just installed the preview version for .NET 10 and I'm glad to see that it's very nearly working perfectly out of the box.&lt;/p&gt;
&lt;p&gt;For background, my use case is using C# to script builds in GitHub actions, particularly the static generator for this blog. For some time I've been (more or less) keeping up my project &lt;a href="https://github.com/IanWold/metalsharp"&gt;Metalsharp&lt;/a&gt;, a C# version of the JS static site generator &lt;a href="https://metalsmith.io/"&gt;Metalsmith&lt;/a&gt;. I'm really happy with the project and I and other colleagues have used it successfully in a pretty wide set of applications. The only problem is that C# doesn't &lt;em&gt;really&lt;/em&gt; work as a scripting language; it's compiled. It is a bit ridiculous to suggest that a build agent using my tool should include a build step to build the script that builds the site, so I turned to the &lt;a href="https://github.com/dotnet-script/dotnet-script"&gt;dotnet-script&lt;/a&gt;, a dotnet tool that allows a single CS-esque file to be executed with a single command.&lt;/p&gt;
&lt;p&gt;There were a few troubles with that workflow: The build agent needed to include a separate step to install the dotnet-script tool, I generally prefer not to rely on third party things for very foundational components of any project, and the builds took a long time to execute. What would take a fraction of a second on my local machine debugging with a CSPROJ file took 60 seconds in GitHub actinos with dotnet-script. The biggest problem though was the local development experience: I've never been able to get any intellisense for dotnet-script in any IDE, so I'd have to build the project to see errors. Alas, there was no other way to run a single CS file without a build step.&lt;/p&gt;
&lt;p&gt;This all prevented me from considering Metalsharp to have reached v1, and I haven't yet published or publicized the project. I hope this sets up how excited I am that .NET 10 is going to be including this feature: I'll be able to &amp;quot;officially&amp;quot; release this years-long project if it's a stable foundation for development and production.&lt;/p&gt;
&lt;p&gt;As I mentioned, I did get it up and going and the initial indication is a great success. Importantly, it works out of the box exactly how I'd expect; there are no surprises or hidden configurations or the like to get it to work locally or in my GitHub Actions. It does indeed solve most of the problems I've had with dotnet-script: I need no extra step to include the feature with &lt;code&gt;dotnet&lt;/code&gt;, the feature is a first-party &lt;em&gt;part&lt;/em&gt; of .NET, and what had been taking 60 seconds to run in GitHub is now taking 8. To me, this is a runaway success, except for this one issue: it &lt;em&gt;still&lt;/em&gt; doesn't have intellisense support, at least not that I can find.&lt;/p&gt;
&lt;p&gt;This is a preview feature of .NET so I'm expecting that this will be fixed when it is released in November, but for the time being it is still quite annoying. Nonetheless, in the next couple of months I'm going to be preparing my Metalsharp project to bump to v1 with .NET 10, and I'm very happy about that!&lt;/p&gt;
&lt;p&gt;You can read more about the feature on &lt;a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/"&gt;Microsoft's post&lt;/a&gt; or look at the &lt;a href="https://github.com/IanWold/ianwold.github.io/blob/master/.github/workflows/build.yml"&gt;build configuration for this blog&lt;/a&gt; to learn more about this feature.&lt;/p&gt;
</description>
      <pubDate>Sun, 22 Jun 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-06-22T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">dotnet_9_csharp_13</guid>
      <link>https://ian.wold.guru/Posts/dotnet_9_csharp_13.html</link>
      <title>Hot off the Press: .NET 9, C# 13</title>
      <description>&lt;p&gt;The new .NET and C# are out as of yesterday, and I'm underwhelmed. Well, maybe since we've known for some time what was in this release I should just be whelmed.&lt;/p&gt;
&lt;p&gt;AI being the current hype, Microsoft is placing front and center what it can find related to AI. On the positive side, this means some updates to &lt;code&gt;Tensor&amp;lt;&amp;gt;&lt;/code&gt; and vectors, though these don't impact me on the day-to-day. Most of what they've got seems to just be a version bump for AI clients from OpenAI and such. In fact, I'm a bit hazy on what real problems the set of &amp;quot;AI&amp;quot; features are solving other than giving them the ability to do presentations about how &amp;quot;.NET 9 enhances support for AI.&amp;quot; If you've got more information please comment below.&lt;/p&gt;
&lt;p&gt;Then they're really pushing .NET Aspire, which I continue to skeptically watch; is Aspire not a conspiracy by big Azure to sell more cloud? I can't imagine that's not part of the calculus, but Aspire has a couple cool things, the local dashboard is neat, as is the container orchestration. It still seems like a quite opinionated sort of boilerplate I need to write to get it up and going when existing tools (docker compose and the tooling around that) take just as much code, have been around longer, and give me most of the same advantages.&lt;/p&gt;
&lt;p&gt;Aspire has some neat logging improvements now on their nifty dashboard, but you get this with other standard otel tools like &lt;a href="https://www.honeycomb.io/"&gt;Honeycomb&lt;/a&gt;. Even the local development experience doesn't seem to be necessarily &lt;em&gt;better&lt;/em&gt; than the experience with existing tools, just &lt;em&gt;different&lt;/em&gt;. Aspire does make it very easy to throw on distributed components once I've got an app up and going with it, but not &lt;em&gt;easier&lt;/em&gt; than my existing setup. I feel like I have to be missing something here, but if I'm not then this is just a more architecturally-constraining way to distribute your application. Am I becoming the old man yelling at the cloud? If you have an answer, please let me know.&lt;/p&gt;
&lt;p&gt;Then there's a decent smattering of improvements across the board for ASP, SignalR, and Blazor. I'm excited to update a few applications for these benefits, but they're just the standard sort of regular improvements. Which is good - don't get me wrong - just whelming.&lt;/p&gt;
&lt;p&gt;The coolest such improvemnt is that SignalR now allows covariant types, with the caveats that you're using &lt;code&gt;System.Text.Json&lt;/code&gt; and are comfortable throwing some attributes on your models. I'm going to see about setting up covariant result types with this, possibly as a follow up to my article on &lt;a href="https://ian.wold.guru/Posts/roll_your_own_csharp_results.html"&gt;properly using the result pattern in C#&lt;/a&gt; for use in SignalR.&lt;/p&gt;
&lt;p&gt;C# doesn't have a lot going on, which is maybe a welcome change of pace considering each version since 7 has introduced very new things. It does leave a conspicuously discriminated-union-shaped hole in these release notes, though. To be fair, it's my impression that such an addition has a fair enough amount of complexity that a slower and more deliberate approach is needed. Fair.&lt;/p&gt;
&lt;p&gt;The annual &lt;a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/"&gt;performance article by Stephen Toub&lt;/a&gt; is excellent as always, and excitingly (for me at least) seems to reveal a lot of work put into compilation in this release. That's the best news - it suggests improvements across the board.&lt;/p&gt;
</description>
      <pubDate>Wed, 13 Nov 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-11-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">dotnet_from_framework_to_10</guid>
      <link>https://ian.wold.guru/Posts/dotnet_from_framework_to_10.html</link>
      <title>.NET: From Framework to 10</title>
      <description>&lt;p&gt;It's been more than six years since the last major version of .NET Framework (4.8) was launched along with C# 7.3, and the .NET world has come a huge way in that time. Last week Microsoft released .NET 10 and C# 14, continuing a trend of releases that significantly rework how software is written on the .NET platform. While Microsoft, and most brand-new .NET development along with them, have been making these strides, there's still plenty of older applications still on .NET Framework; as a result, there are plenty of engineers focused on maintaining these codebases that feel increasingly disconnected with modern .NET practices.&lt;/p&gt;
&lt;p&gt;Each year now brings a new release of .NET (that is &amp;quot;.NET&amp;quot; and not &amp;quot;.NET Framework&amp;quot; or &amp;quot;.NET Core&amp;quot;) along with a new release of C#, and each of these releases is a significant lift to the libraries, language, tooling, best practices, and standards. C# has become much more accommodating (and in some cases requiring) functional programming styles, while .NET has introduced sweeping new features across all of its workloads. This huge amount of change is reflected in the ways engineers think about, architect, and productionalize applications in .NET.&lt;/p&gt;
&lt;p&gt;I can't give a perfectly thorough explanation of &lt;em&gt;all&lt;/em&gt; the ways that .NET is different now than it was in 2019, but I do want to type up &lt;em&gt;some&lt;/em&gt; thoughts on what's going on. Each year it becomes more and more difficult for .NET Framework engineers to make the hop over to the latest .NET with the same level of productivity and familiarity. It's not just the new .NET APIs or the new C# keywords but the styles and paradigms of the development and the &amp;quot;why&amp;quot;s motivating the new changes. 10 seems like a good version number for a catch-up, so I want some writing here that can serve as a jumping-off point for .NET Framework engineers wanting to get up-to-speed with the latest toys.&lt;/p&gt;
&lt;p&gt;It is difficult to, for any individual update, explain the precise way that the new features have resulted in a different style of development. This is particularly so since many of these changes have been made in order to accommodate development practices that had already emerged; the language and framework evolved to meet the requests of engineers that have been influenced from other sources. Thus, by giving an explanation of modern .NET practices by starting &lt;em&gt;from&lt;/em&gt; these updates, I'm missing out on the other force that is moving .NET development along, and that will need to be a future post from me. Nonetheless, I think that the majority of .NET engineers are not on this vanguard but are instead wanting to adopt these new features into their existing workflow, and this is where I see the utility of tying all these into an explanation of &amp;quot;modern .NET.&amp;quot;&lt;/p&gt;
&lt;p&gt;As a fair forewarning though, I'm largely going off of memory and some incomplete historic documentation from Microsoft to figure everything out historically so there might be some inaccuracies around the edges here. However, everything I say you can do in C# and .NET now is accurate, maybe the history is a bit wonky. Furthermore, I'm also not giong to say anything about F#. I enjoy using F# but I use C# professionally, so I want to focus on how changes to .NET and C# have affected how we do development with those two. Finally, I will not be explaining any improvements to EntityFramework because a polished dung is still a dung.&lt;/p&gt;
&lt;h2&gt;.NET History: Framwork, Standard, and Core&lt;/h2&gt;
&lt;p&gt;I think a (very small) bit of history can help understand most of the reasons that have motivated the lion's share of changes in .NET and help disambiguate terms. .NET Framework came about ages ago to be the standard library for the (then) new C# language, and expanded and evolved quite rapidly alongside the language to support all the different kinds of applications Microsoft wanted to support. ASP was added as a web framework, WinForms (then WPF then a mess of other things) supported desktop GUIs, WCF supported SOAP, Silverlight allowed engineers to frustrate everyone who wanted to watch the 2008 Olympics, and so on.&lt;/p&gt;
&lt;p&gt;As software engineers we understand very well that if requirements expand and change too rapidly and haphazardly, the sooner a rewrite of the system is going to be needed, but we usually try to stave off a rewrite until &lt;em&gt;something&lt;/em&gt; happens to make it necessary. What happened to .NET a bit over ten years ago is it needed to run on Linux among a host of other factors (I'm boiling it down significantly). It came to be that a rewrite and some breaking changes needed to be made.&lt;/p&gt;
&lt;p&gt;So, Microsoft embarked on creating an alternative standard library for C#, which they called .NET Core, and released it in 2016. They also created a separate deal called &amp;quot;.NET Standard&amp;quot; to act as a standard between .NET Framework and .NET Core, guaranteeing certain similar APIs in both standard libraries to ease transitioning from one to another, or writing code that could be guaranteed to work in both libraries.&lt;/p&gt;
&lt;p&gt;.NET Core progressed through a few versions before Microsoft quickly decided that it would be best to only develop updates for the new .NET Core going forward. In 2019 they released the last major version of .NET Framework, but also to demark the change they decided to be rid of the &amp;quot;Core&amp;quot; name as well. The last version of .NET Core, 3.1, was developed into the first version of &amp;quot;.NET&amp;quot;, which they began at version 5 as the lowest major version number not common to either of its predecessors. Each year since .NET 5 was released, Microsoft has released a new major version of .NET along with a new major version of the C# language, and this period has seen the most significant updates to the framework and language.&lt;/p&gt;
&lt;h2&gt;.NET Core: 1.0 to 3.1&lt;/h2&gt;
&lt;p&gt;The various versions of .NET Core essentially ended up serving as the ramp-up period for the new .NET development, and did not very significantly diverge from .NET Framemwork but for having full support for Linux and MacOS.&lt;/p&gt;
&lt;p&gt;The first super duper thing introduced with it was the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet"&gt;dotnet CLI tool&lt;/a&gt; exposing actions like creating a new app, building it, restoring packages, and the like. This acts in a similar way to the compilers of most programming languages, facilitating doing C# development in a similar manner to any other language. Go has commands like &lt;code&gt;go run .&lt;/code&gt;, and now .NET has &lt;code&gt;dotnet run&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;.NET Core 3.0 is the first interesting .NET version to me. It introduced the ability to publish an app as a single file; instead of having to ship a bunch of DLLs and support files, the C# app gets contained in one &lt;code&gt;.exe&lt;/code&gt; or whatever format it is for the target OS you select.&lt;/p&gt;
&lt;p&gt;.NET Core 3.0 shipped along with C# 8, which had a huge number of updates to the language. The most significant feature was the nullable reference types. Null was, previously, very tricky with reference types, since they always had the option to be null, requiring huge amount of null checks and lots of time lost to reference-type variables unexpectedly becoming null in prod. The solution was to allow the nullable &lt;code&gt;?&lt;/code&gt; operator to be added for reference types, and a compiler directive to tell C# that it should throw warnings or errors if null checks weren't explicitly added around these.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;string? myString = ...;
PrintLength(myString); // Error: myString can be null

public void PrintLength(string s) =&amp;gt;
    Console.WriteLine(s.Length());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the csproj, the &lt;code&gt;Nullable&lt;/code&gt; property needs to be set, and now is by default:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;Nullable&amp;gt;enable&amp;lt;/Nullable&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm going to dump a bunch of little things here:&lt;/p&gt;
&lt;p&gt;One of the cooler things in C# 8 is default interface members:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public interface IThing
{
    string GetName() =&amp;gt; &amp;quot;Unnamed&amp;quot;;
}

public class Thing : IThing {}

Console.WriteLine((new Thing()).GetName()); // Prints &amp;quot;Unnamed&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well as &amp;quot;static abstract&amp;quot; members:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public interface IThing
{
    static abstract string Name { get; }
}

public class Thing : IThing
{
    public static string Name =&amp;gt; &amp;quot;I'm Thing&amp;quot;; // Required to be present to compile
}

public void PrintName&amp;lt;T&amp;gt;() where T : IThing =&amp;gt;
    Console.WriteLine(T.Name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Switch expressions were also introduced, allowing you to treat a &lt;code&gt;switch&lt;/code&gt; as a value:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var myThing = myIntThing switch
{
    1 =&amp;gt; &amp;quot;one&amp;quot;,
    2 =&amp;gt; &amp;quot;two&amp;quot;,
    _ =&amp;gt; &amp;quot;larger than two&amp;quot;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This supports very functional-looking development doing pattern matching on the shape of arguments in a method, though this example is admittedly very contrived:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public bool IsEmpty&amp;lt;T&amp;gt;(IEnumerable&amp;lt;T&amp;gt; list) =&amp;gt; list switch
{
    var l when l.Count &amp;gt; 0 =&amp;gt; true,
    _ =&amp;gt; false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Local functions are pretty significant and just support keeping appropriate scope for methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public void DoThing()
{
    string GetString() =&amp;gt;
        &amp;quot;hi&amp;quot;;

    Console.WriteLine(GetString());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I make huge use of null-coalescing assignment for lazy loading properties:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class Thing
{
    private MyLoadableThing? _prop;
    public MyLoadableThing Prop =&amp;gt; _prop ??= new MyLoadableThing();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A huge quality of life improvement came with &lt;code&gt;..&lt;/code&gt; as a range operator like many other languages have (this example taken from MS's docs):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;int[] numbers = [0, 10, 20, 30, 40, 50];
int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of the most interesting additions to the .NET world with .NET Core 3.0 was Blazor, which takes a bit of explaining for the uninitiated. For some time, there's been an amount of effort to create an assembly language for the web - Web Assembly or WASM - understood by all browsers, that can be as ubiquitously implemented as JavaScript but that is, well, assembly, instead of JS. This has resulted in a number of languages being able to target WASM as a platform separate to Windows, Linux, and so on.&lt;/p&gt;
&lt;p&gt;Microsoft figured that with .NET Core intending to support multiple platforms that WASM should be no different, but of course &lt;em&gt;the&lt;/em&gt; use case for WASM is web apps, so Microsoft developed Blazor as both the .NET runtime for WASM as well as the framework for developing a web UI in C# for WASM.&lt;/p&gt;
&lt;p&gt;I won't dig too much into Blazor here, that can be a whole separate thing. However, I'll point you to an open source app I have: &lt;a href="https://ian.wold.guru/Posts/free_planning_poker.html"&gt;FreePlanningPoker.io&lt;/a&gt;, which is a simple Blazor + SignalR app that you can dive into for a taste of Blazor!&lt;/p&gt;
&lt;h2&gt;.NET 5 and C# 9&lt;/h2&gt;
&lt;p&gt;Being the first version to &amp;quot;consolidate&amp;quot; the standard library post-Framework, there wasn't too much added to the APIs here. Most significantly is probably that ASP introduced the option to self-host instead of needing to rely on IIS, making it much easier to deploy containerized.&lt;/p&gt;
&lt;p&gt;C# 9 was released in tandem, and just like the previous release had a huge number of things included. I think the most significantly new feature in C# in the last five years was introducted in this version: records. A record is a type of class in C# that is primarily designed for immutable data; that is, a class where no properties change. These are used extensively in functional languages and the compiler can make a lot of optimizations for immutable data when you use them.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public record Person(string Name, int Age);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is &lt;em&gt;kind of&lt;/em&gt; equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class Person
{
    public string Name { get; private set; }

    public int Age { get; private set; }

    public Person(string name, int Age) { /* assign props */ }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Except the Name and Age properties can never be modified (either from inside or outside the record). Further, records have deep value equality, so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var first = new Person(&amp;quot;John&amp;quot;, 30);
var second = new Person(&amp;quot;John&amp;quot;, 30);
Console.WriteLine(first == second); // Outputs true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Functional styles with immutable records will typically assign new values when record data needs to change, and C# supports this pattern now with the &lt;code&gt;with&lt;/code&gt; keyword:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;Person Birthday(Person person) =&amp;gt;
    person with { Age = person.Age + 1 };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other really awesome change is a quality of life improvement that Program.cs does &lt;em&gt;not&lt;/em&gt; need a class, Main method, or even a namespace now. The following will compile as a valid C# program:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;Console.WriteLine(&amp;quot;Hello, World!&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.NET 6 and C# 10&lt;/h2&gt;
&lt;p&gt;.NET 6 started seeing some new things introduced in the API level. Most notably though is MAUI, &amp;quot;modern application UI&amp;quot;, which was created to replace Xamarin and WPF as the cross-platform way to develop UIs in C#. This is a huge disappointment: it was buggy and incomplete on its release and it has not improved very much since.&lt;/p&gt;
&lt;p&gt;In the ASP level, .NET 6 introduced &amp;quot;minimal APIs,&amp;quot; which were developed to be able to have as easy a proof-of-concept story as other languages. For example, in Python, setting up a hello world endpoint is trivially simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from flask import Flask

app = Flask(__name__)

@app.route(&amp;quot;/&amp;quot;)
def hello_world():
    return &amp;quot;&amp;lt;p&amp;gt;Hello, World!&amp;lt;/p&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In ASP this had previously required creating a controller class, and while that's not &lt;em&gt;difficult&lt;/em&gt;, it's not super speedy feels like the future, so now we've got this as an option alongside controllers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.MapGet(&amp;quot;/&amp;quot;, () =&amp;gt; &amp;quot;Hello, World&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is actually astonishing for rapid development. Kudos here. Other great thing: lots of Open Telemetry support. I just use the regular OTEL added with the templates nowadays so I'm kind of dumb as to what specifically was done here, but it was done.&lt;/p&gt;
&lt;p&gt;On the C# side, the super cool deal everyone was talking about was file-scoped namespaces. Now, instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;namespace MyApp
{
    class MyClass
    {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;namespace MyApp;

class MyClass
{
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I refuse to read code if it is &amp;gt;= version 10 and does not do this. Another cool namespace thing you can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;global using WhateverNamespace; // now no other file needs &amp;quot;using WhateverNamespace;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then the functional support is extended through pattern matching with &amp;quot;property patterns&amp;quot;, this from MS's docs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;if (e is MethodCallExpression { Method.Name: &amp;quot;MethodName&amp;quot; })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be a bit unintuitive at first, but it fits right in line with how a lot of languages with better pattern matching support do. Pattern matching is hugely important for functional patterns, and can be kind of fun:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public abstract record Shape;

public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;

public static double Area(Shape shape) =&amp;gt; shape switch
{
    Circle { Radius: var r } =&amp;gt; Math.PI * r * r,
    Rectangle { Width: var w, Height: var h } =&amp;gt; w * h,
    _ =&amp;gt; throw new ArgumentException(&amp;quot;Unknown shape&amp;quot;) // required for exhaustiveness in the switch expression
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.NET 7 and C# 11&lt;/h2&gt;
&lt;p&gt;I remember .NET 7 being very cool, but looking back at the historical documentation I can't really remember this happening. If I'm remembering correctly, .NET 7 came out a little over a year after I started at Crate &amp;amp; Barrel, and I had upgraded some brand new microservices from .NET 5 to .NET 7 right then because I could. What a deal!&lt;/p&gt;
&lt;p&gt;The really cool thing here was you can now choose to have .NET generate a Dockerfile when you publish your app, instead of just compiling it. This makes it so you don't need to hand-write the Dockerfile, which is a really nifty feature and has supported being able to debug directly in Docker from Visual Studio. Maybe Code also. I don't use it very much but no doubt it's nifty.&lt;/p&gt;
&lt;p&gt;This was also the year that &lt;a href="https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/"&gt;Stephen Toub's annual performance improvement report&lt;/a&gt; became the talk of the industry. He'd been writing these for some time, but this one was really overwhelming at the time. To say these articles are &amp;quot;huge&amp;quot; would be an understatement as large as these articles are; they're incredibly thorough, meticulous, and technical, but a &amp;quot;TLDR&amp;quot; on the .NET 7 article sums up the cause of its impact in this particular release:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TL;DR: .NET 7 is fast. Really fast. A thousand performance-impacting PRs went into runtime and core libraries this release, never mind all the improvements in ASP.NET Core and Windows Forms and Entity Framework and beyond. It’s the fastest .NET ever. If your manager asks you why your project should upgrade to .NET 7, you can say “in addition to all the new functionality in the release, .NET 7 is super fast.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;.NET 7's focus on performance continued past this release into each of the subsequent releases, and each year since I've observed improvements from each version bump we do. .NET gets a lot of flack for being bloaty; .NET 7 is Microsoft righting that course.&lt;/p&gt;
&lt;p&gt;Apart from supporting dark mode for developer exception pages, ASP had a lot of small quality of life features, but it's tough to parse out a single one that's changed how development happens. By this point too, development on WinForms and WPF had largely stalled, while MAUI and Blazor made a couple steps forward but not enough for me to really enjoy them. MAUI remained half-baked and Blazor remained difficult to debug.&lt;/p&gt;
&lt;p&gt;C# 11 brought a bunch of things that have really influenced how I do development.&lt;/p&gt;
&lt;p&gt;The feature they called &amp;quot;generic math&amp;quot; had a huge amount of discussion on the C# language design repository, and launched to much acclaim. Essentially they had all the numeric types in C# implement &lt;code&gt;INumber&amp;lt;self&amp;gt;&lt;/code&gt;, so &lt;code&gt;Integer&lt;/code&gt; implements &lt;code&gt;INumber&amp;lt;Integer&amp;gt;&lt;/code&gt; for example. This allows math to be done indiscriminately of whether the numeric type is &lt;code&gt;double&lt;/code&gt; or &lt;code&gt;int&lt;/code&gt; or whatever. From MS's docs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static TResult Sum&amp;lt;T, TResult&amp;gt;(IEnumerable&amp;lt;T&amp;gt; values)
    where T : INumber&amp;lt;T&amp;gt;
    where TResult : INumber&amp;lt;TResult&amp;gt;
{
    TResult result = TResult.Zero;

    foreach (var value in values)
    {
        result += TResult.Create(value);
    }

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Very cool! I never use it, but indeed there are many applications for it.&lt;/p&gt;
&lt;p&gt;What I do use all the time, particularly for SQL scripts, is raw string literals:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var myString = &amp;quot;&amp;quot;&amp;quot;
    Hello,
    World
    &amp;quot;&amp;quot;&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can specify how many curlies are needed for interpolation by adding &lt;code&gt;$&lt;/code&gt;s:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var hi = &amp;quot;Hello&amp;quot;;
var myString = $$$$$&amp;quot;&amp;quot;&amp;quot;
    {{{{{hi}}}}},
    World
    &amp;quot;&amp;quot;&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;File types are also something I make a lot of use of. Instead of private nested classes, &lt;code&gt;file&lt;/code&gt; will limit the scope of a type to a file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// No longer do:
public class MyClass
{
    private record MyRecord(../);

    // Use MyRecord
}

// Now do:
file record MyRecord(...);

public class MyClass
{
    // Use MyRecord
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Saves nesting, makes it easier to navigate files.&lt;/p&gt;
&lt;p&gt;Continuing the support for pattern matching (and functional patterns by extension), this version introduced list pattern matching. You can do some wild things:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;x switch
{
    [1, 2, 3] =&amp;gt; //matches if x is int[] { 1, 2, 3 }
    [1, 2, 3, ..] =&amp;gt; // matches if x is int[] { 1, 2, 3, 4, etc }
    [_, _, ..] =&amp;gt; // matches if x has at least 2 items (_ is a throwaway identifier, the values are not kept)
    [.., 2] =&amp;gt; // matches if x ends with 2
    [ &amp;gt;1, ..] =&amp;gt; // matches if the first element of x is greater than 1
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.NET 8 and C# 12&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ian.wold.guru/Posts/book_club_11-2023.html"&gt;This is the first .NET release I did a blog about!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I remember this release being particularly good for ahead-of-time (AOT) compilation. .NET Core and its successor have focused on providing support for AOT compilation, which differs from the typical .NET compilation strategy of just-in-time (JIT) by compiling the entire application into native machine code, instead of compiling into IL and relying on a pre-installed .NET runtime on the target machine.&lt;/p&gt;
&lt;p&gt;Compiling AOT obviously has more limitations as .NET is primarily designed for JIT, but it is an important feature to support the portability and, in some cases, performance requirements of many applications. After .NET 8 I became quite comfortable using AOT.&lt;/p&gt;
&lt;p&gt;I also remember that the &lt;code&gt;System.Text.Json&lt;/code&gt; library became very good in this release, as it was after .NET 8 that I replaced a lot of code that had used Newtonsoft with the new one.&lt;/p&gt;
&lt;p&gt;This release also had a significant upgrade for the standard dependency injection framework with keyed services, letting you &amp;quot;key&amp;quot; a service implementation with a string:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;builder.Services.AddKeyedScoped&amp;lt;IService, ServiceOne&amp;gt;(&amp;quot;one&amp;quot;);
builder.Services.AddKeyedScoped&amp;lt;IService, ServiceTwo&amp;gt;(&amp;quot;two&amp;quot;);

app.MapGet(&amp;quot;/&amp;quot;, ([FromKeyedService(&amp;quot;two&amp;quot;)] IService service) =&amp;gt; ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don't say that keyed services are significant because I use them, but because I now have to be cognizant of them whenever I implement some DI thing!&lt;/p&gt;
&lt;p&gt;In the web development world, there were a lot of small things that, looking back on them now, I do not use. There are a couple neat things though.&lt;/p&gt;
&lt;p&gt;SignalR, for one, got a new &lt;code&gt;WithStatefulReconnect&lt;/code&gt; feature that a client connection can specify to automatically reconnect with the same client id when it's disconnected. This, however, relies on the same SignalR server staying alive (as it remembers the client id in its memory). This makes it more difficult to deploy SignalR servers unless you have access to some fancy deployment management tooling to keep servers with outstanding connections alive after new versions are deployed. I stopped using it after a bit.&lt;/p&gt;
&lt;p&gt;Blazor got a bit of a major rejigger on this release, allowing us to blend server- and client- side Blazor. You can now have a server-rendered Blazor application that has some &lt;em&gt;components&lt;/em&gt; that execute in WASM on the client, or you can even wriet a SPA that initially is server-rendered but then seamlessly transitions itself into a full WASM client once the client is fully downloaded, eliminating long startup times. They also introduced &amp;quot;Blazor Hybrid,&amp;quot; allowing an electron-like experience with Blazor and MAUI, but MAUI is still bad.&lt;/p&gt;
&lt;p&gt;Blazor applications are &lt;em&gt;still&lt;/em&gt; 10MB by default, so very large, and the whole server-to-client transition thing is a bit of a workaround, but it's not uncommon for SPA libraries to support this and Blazor makes it quite easy.&lt;/p&gt;
&lt;p&gt;C# 12 introduced two huge quality of life features: primary constructors and collection expressions.&lt;/p&gt;
&lt;p&gt;When records were introduced in C# 9, they had &amp;quot;primary constructors&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public record Person(string Name, int Age);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These arguments are then compiled into public, read-only properties in the record. C# 12 made primary constructors available for classes, but they behave differently:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class PersonService(PersonRepository repository, ILogger&amp;lt;PersonService&amp;gt; logger)
{

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the argument identifiers are lower-case: for classes, primary constructor arguments compile into private, read-only fields. In fact, the above is exactly equivalent to the following in previous versions:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class PersonService
{
    private readonly PersonRepository repository;
    private readonly ILogger&amp;lt;PersonService&amp;gt; logger;

    public PersonService(PersonRepository repository, ILogger&amp;lt;PersonService&amp;gt; logger)
    {
        this.repository = repository;
        this.logger = logger;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The primary constructor, then, significantly reduces the amount of things we need to write, particularly for services with many dependencies. One thing to note also is the primary constructor gets rid of the need to preface private fields with an underscore (&lt;code&gt;_&lt;/code&gt;). In previous versions, most C# engineers would have preferred to write the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class PersonService
{
    private readonly PersonRepository _repository;
    private readonly ILogger&amp;lt;PersonService&amp;gt; _logger;

    public PersonService(PersonRepository repository, ILogger&amp;lt;PersonService&amp;gt; logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public Person? GetPerson(int id)
    {
        var personResult = _repository.GetPersonById(id);

        if (personResult is not PersonDao p)
        {
            _logger.LogInfo(&amp;quot;Unable to find person {Id}&amp;quot;, id);
            return null;
        }

        return new(p.Name, p.Age);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, it's now typically preferred to eschew the underscore entirely:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class PersonService(PersonRepository repository, ILogger&amp;lt;PersonService&amp;gt; logger)
{
    public Person? GetPerson(int id)
    {
        var personResult = repository.GetPersonById(id);

        if (personResult is not PersonDao p)
        {
            logger.LogInfo(&amp;quot;Unable to find person {Id}&amp;quot;, id);
            return null;
        }

        return new(p.Name, p.Age);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Collection expressions are the other great feature in this release. What this feature does is it makes the following a literal value in C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, I can do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public int[] GetInts() =&amp;gt;
    [1, 2, 3];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is huge! Previously I'd need to do:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public int[] GetInts() =&amp;gt;
    new int[] {1, 2, 3};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the spread operator this saves a lot of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static T[] Concat&amp;lt;T&amp;gt;(this T[] first, T[] second) =&amp;gt; [..first, ..second];
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.NET 9 and C# 13&lt;/h2&gt;
&lt;p&gt;Having done such a significant amount of development to this point, .NET 9 slowed down a bit and, like .NET 7, had a huge focus on performance. Not that .NET 8 didn't have major performance improvements, but .NET 9 really did. When &lt;a href="https://ian.wold.guru/Posts/dotnet_9_csharp_13.html"&gt;I wrote about this release&lt;/a&gt; I mostly complained about Microsoft pushing AI and Aspire, and I continue to hold this opinion.&lt;/p&gt;
&lt;p&gt;There were some fun goodies though. I enjoy my LINQ and we got new &lt;code&gt;CountBy&lt;/code&gt; and &lt;code&gt;AggregateBy&lt;/code&gt; methods, which do what they sound like they do.&lt;/p&gt;
&lt;p&gt;In this version the compiler started recommending the following when doing any kind of regex matching (example from MS's docs):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[GeneratedRegex(@&amp;quot;\b\w{5}\b&amp;quot;)]
private static partial Regex FiveCharWordProperty { get; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses source generators for regex, which does seem to be a whole lot faster in parsing regex. This is good news all around, I know plenty of applications with a little bit of regex here and there (mostly in a validation layer) that benefit from this.&lt;/p&gt;
&lt;p&gt;A big improvement to async work came with &lt;code&gt;await foreach&lt;/code&gt; and &lt;code&gt;Task.WhenEach&lt;/code&gt; (example also from MS's docs):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;await foreach (Task&amp;lt;string&amp;gt; t in Task.WhenEach(first, second, third))
{
    Console.WriteLine(t.Result);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other great async improvement is the new &lt;code&gt;System.Threading.Lock&lt;/code&gt; object, which provides a better interface and language integration for locking assets in async operations (instead of locking on an &lt;code&gt;object&lt;/code&gt;, use the &lt;code&gt;Lock&lt;/code&gt; API).&lt;/p&gt;
&lt;p&gt;On the web side, ASP got &lt;code&gt;app.MapStaticAssets()&lt;/code&gt;, which is a great builtin feature for shipping static assets, like a React SPA, with compression and etags built in.&lt;/p&gt;
&lt;p&gt;Blazor got constructor injection for Razor components! Yes, before this you had to use property injection. No joke!&lt;/p&gt;
&lt;p&gt;SignalR received a number of improvements. Polymorphic type support was big for me when it came out, but it also received a lot of updates to the telemetry it omits. This was really key for a lot of applications; having a complicated websockets app with insufficient telemetry was probably not great for a lot of folks.&lt;/p&gt;
&lt;p&gt;Finally, ASP also introduced a &lt;code&gt;HybridCache&lt;/code&gt; type that is capable of being both in-memory and distributed, handling a lot of the complexity of having those two tiers of cache. Very handy! I constantly insist that caches are difficult and hazardous; having a well-constructed first-party implementation with an appropriately restrictive API allows a wider adoption of safer caches.&lt;/p&gt;
&lt;p&gt;On the C# side, there's a bunch of small things. With how impactful previous features were, it is good they slowed down a bit.&lt;/p&gt;
&lt;p&gt;My favorite is being able to use any collection type for &lt;code&gt;params&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public void DoThing(params IEnumerable&amp;lt;string&amp;gt; args) =&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The language also introduced &lt;code&gt;^&lt;/code&gt; as an operator you can use when indexing arrays, meaning &amp;quot;from the end&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var myArray = new int[5];
myArray[^1] = 12;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;myArray&lt;/code&gt; then has &lt;code&gt;12&lt;/code&gt; at the 1th position from the end. Interesting!&lt;/p&gt;
&lt;h2&gt;.NET 10 and C# 14&lt;/h2&gt;
&lt;p&gt;As of my writing this, .NET 10 and C# 14 have &lt;em&gt;just&lt;/em&gt; been released a few days ago. Exciting!&lt;/p&gt;
&lt;p&gt;This release is similar to the last in that the development has settled into a more manageable pace; it keeps the focus on broad performance improvements, incremental nice-to-haves, and rounding out C#'s support for modern development patterns.&lt;/p&gt;
&lt;p&gt;One &lt;em&gt;very&lt;/em&gt; nice addition is &lt;code&gt;WebSocketStream&lt;/code&gt;, which essentially lets you treat a web sockets connection like a stream. I think I may be using this a lot!&lt;/p&gt;
&lt;p&gt;My favorite nice-to-have is &lt;code&gt;dotnet run program.cs&lt;/code&gt;, which completes the scripting capabilities in C#. With this feature, I do not need a SLN or CSPROJ in order to run a C# script. If I have a file &lt;code&gt;sayhi.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;Console.WriteLine(&amp;quot;Hello, World!&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With nothing else in the directory, I can &lt;code&gt;dotnet run sayhi.cs&lt;/code&gt; and it runs. Super cool! This isn't just great for local scripting, I use this to generate my site!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My site has a single &lt;a href="https://github.com/IanWold/ianwold.github.io/blob/master/build.cs"&gt;build.cs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Which I then run to generate this static site &lt;a href="https://github.com/IanWold/ianwold.github.io/blob/master/.github/workflows/build.yml#L47"&gt;in a GitHub action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the web development side, Blazor got a huge number of quality-of-life improvements that make developing with it - even a full WASM app - actually doable. I have a few Blazor projects and since upgrading to the .NET 10 previews it's been actually very nice. I won't go into any particular features but to say that if you've not enjoyed Blazor in the past, this one might make you more comfortable with it.&lt;/p&gt;
&lt;p&gt;Another thing I'm eager to dig into is an overhaul of JSON Patch. PATCH is still a difficult thing to deal with, but there's a lot of applications for it and a new, robust, standards-compliant implementation looks good.&lt;/p&gt;
&lt;p&gt;C# 14 has two features I'm very excited for that have been a long time in the making, maybe more than 10 years each.&lt;/p&gt;
&lt;p&gt;First is the &lt;code&gt;field&lt;/code&gt; keyword. For too long, we've had to manually create backing fields for properties:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class MyClass
{
    private LazyService? _service;
    public LazyService Service =&amp;gt;
        _service ??= new(...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is unfortunate because auto properties compile to include their own scoped field anyway, but the programmer has never had access to this. Having to do the above exposes the backing field internally in the class. Should instance methods use &lt;code&gt;Service&lt;/code&gt; or &lt;code&gt;_service&lt;/code&gt;? The answer is definitely &lt;code&gt;Service&lt;/code&gt; but that gets ignored sometimes. Now, we can use &lt;code&gt;field&lt;/code&gt; instead to ensure the field is properly scoped to the property:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class MyClass
{
    public LazyService Service =&amp;gt;
        field ??= new(...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next super-cool feature is &amp;quot;extension members.&amp;quot; Not really sure where they got that name but that's what we're going with. For a very long time now, the C# language design team has dreamed of what they call &amp;quot;extension everything,&amp;quot; extending the langauge to support that any kind of extensions could be made for types. Many languages, not just functional languages, support a much more robust extension system than C#. In fact, C#'s extensions are kind of hacky, essentially just giving a way to turn a static method call into a &lt;code&gt;.&lt;/code&gt;-looking call. Not &lt;em&gt;really&lt;/em&gt; extensions.&lt;/p&gt;
&lt;p&gt;This version of C# lets you do the following wizardry, in a step towards &amp;quot;extension everything&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public record Person(string FirstName, string LastName, int Age);

public static class Extensions
{
    extension (Person)
    {
        public static string Species =&amp;gt; &amp;quot;Homo Sapiens&amp;quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What can I do with that?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;Console.WriteLine(Person.Species); // Outputs &amp;quot;Homo Sapiens&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whoa!! Super cool! It's an &lt;em&gt;actual&lt;/em&gt; extension! But wait, there's more - if I name that type then I can put extensions into the type, similar to how I do with the &lt;code&gt;this&lt;/code&gt; keyword today:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class Extensions
{
    extension (Person person)
    {
        public string FullName =&amp;gt; $&amp;quot;{person.FirstName} {Person.LastName}&amp;quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But ... notice it's a property and not a method! Wooooo!!! You can still do methods too.&lt;/p&gt;
&lt;h2&gt;Bringing Everything Together&lt;/h2&gt;
&lt;p&gt;So, given all of these, what do modern development practices look like today?&lt;/p&gt;
&lt;p&gt;I've mentioned &amp;quot;functional&amp;quot; styles a fair amount. There's three features in particular that support functional-style development: records, pattern matching, and switch expressions. Switch expressions might seem an odd inclusion on this list, but let me demonstrate that it's necessary to include these to really be able to harness functional patterns in C#. Most functional languages rely on function definitions using pattern matching, like this Haskell Fibonacci function:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-haskell"&gt;fib :: Integer -&amp;gt; Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The C# equivalent now looks &lt;em&gt;extremely similar&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static int Fib(int n) =&amp;gt; n switch {
    0 =&amp;gt; 0,
    1 =&amp;gt; 1,
    _ =&amp;gt; Fib(n - 1) + Fib(n - 2)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it's not just an aesthetic similarity at that. Functional patterns will assume functions can be written in this form, and the switch expression is the &lt;em&gt;thing&lt;/em&gt; that allows C# to be able to adopt the patterns 1-to-1.&lt;/p&gt;
&lt;p&gt;A more fun comparison could be made with list patterns, here the classic list sum function:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-haskell"&gt;sum :: [Int] -&amp;gt; Int
sum [] = 0
sum (x:xs) = x + sum xs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is also equivalent in C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static int Sum(IEnumerable&amp;lt;int&amp;gt; l) =&amp;gt; l switch {
    [] =&amp;gt; 0,
    [var x, .. var xs] =&amp;gt; x + Sum(xs),
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To bring records into the fold, these are equivalent to data types that typically exist in functional languages; immutable types are the norm there, and their patterns require these kinds of structures. In particular, functional languages have &lt;em&gt;algebraic data types&lt;/em&gt; (ADTs), which are discrete (closed) unions of other types. Take this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-haskell"&gt;data Expression
    = Const Int
    | Add Expr Expr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is this saying? There is a type &lt;code&gt;Expression&lt;/code&gt; that can be &lt;em&gt;either&lt;/em&gt; a &lt;code&gt;Const&lt;/code&gt; (which will itself contain a single integer), or an &lt;code&gt;Add&lt;/code&gt; (which will contain two &lt;code&gt;Expression&lt;/code&gt; objects). This is modeled like so today with records in C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public abstract record Expression;
public record Const(int Value) : Expression;
public record Add(Expression Left, Expression Right) : Expression;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, C# uses inheritance to signify that an &lt;code&gt;Expression&lt;/code&gt; can be a &lt;code&gt;Const&lt;/code&gt; or an &lt;code&gt;Add&lt;/code&gt;, and this is a typical arrangement when mapping functional ADTs into C#. It is not, however, as precise a translation as the previous examples, because the Haskell structure is saying something kind of different. Where in my C# code I could create a record in some other part of the code and inherit &lt;code&gt;Expression&lt;/code&gt;, I cannot do this in the Haskell example. Technically, we would say that the C# &lt;code&gt;Expression&lt;/code&gt; is not &lt;code&gt;closed&lt;/code&gt;. This presents a difficulty now in how we handle these in functions. Take this Haskell:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-haskell"&gt;evaluate :: Expression -&amp;gt; Int
evaluate (Const n) = n
evaluate (Add a b) = evaluate a + evaluate b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since &lt;code&gt;Expression&lt;/code&gt; is closed in Haskell we will have no problems detected by the compiler, but C# needs an extra line:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static int Evaluate(Expression e) =&amp;gt; e switch {
    Const c =&amp;gt; c.Value,
    Add(var l, var r) =&amp;gt; Evaluate(l) + Evaluate(r),
    _ =&amp;gt; throw new ArgumentException(&amp;quot;Unknown expression&amp;quot;)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This last line is required to complete the switch, since C# currently has no mechanism by which it can close the &lt;code&gt;Expression&lt;/code&gt; type while allowing the inheritance by &lt;code&gt;Const&lt;/code&gt; and &lt;code&gt;Add&lt;/code&gt;. Nonetheless, this is the current proper translation of the functional functions, and is commonly seen in C# today. In fact, this is a great example of what I mentioned in the introduction to this article, that the langauge and framework also evolve to meet the demands of engineers who are using C# in new ways owing to external influence! In this case, the C# langauge team has been pursuing including ADTs fully into the language, a feature which they call &amp;quot;discriminated unions.&amp;quot; The language design team's latest writings on the feature seem to indicate we should see initial support for these in the next version.&lt;/p&gt;
&lt;p&gt;I've also written a bit about some functional patterns that have become particularly popular in the .NET world:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/book_club_10-2023.html"&gt;Functional Patterns in C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/book_club_1-2025.html"&gt;Results, Railways, and Decisions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ian.wold.guru/Posts/roll_your_own_csharp_results.html"&gt;Roll Your Own C# Results&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 21 Nov 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-11-21T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">eight_maxims</guid>
      <link>https://ian.wold.guru/Posts/eight_maxims.html</link>
      <title>Eight Maxims</title>
      <description>&lt;p&gt;&lt;strong&gt;Less is More&lt;/strong&gt; Write less code, use fewer abstractions, be simple and concise. Complexity is easy, simplicity is hard.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Diff Your Commits&lt;/strong&gt; Read your own code before you commit it, before you PR it, and after you merge it. Keep your code tidy and clear.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Document Why, not How&lt;/strong&gt; I can (usually) figure out how to use your code. I can (usually) never figure out why you wrote it that way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Collaborating over Coding&lt;/strong&gt; Software Engineering is 9 parts collaboration and 1 part coding. Be approachable and helpful, reach out to others often.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Respect Legacy Code&lt;/strong&gt; There is much wisdom in legacy code. Understand before judging, embrace before extinguishing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continuous Learning/Continuous Improvement&lt;/strong&gt; Always learn, always try new things, always be open-minded.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Embrace Change&lt;/strong&gt; Change is the only constant. Expect it, embrace it, and code for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lift Everyone Up&lt;/strong&gt; Share knowledge openly, celebrate accomplishments, support and mentor freely. You grow the most when you help others grow.&lt;/p&gt;
</description>
      <pubDate>Wed, 17 Jan 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-01-17T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">end_to_end_encryption_witn_blazor_wasm</guid>
      <link>https://ian.wold.guru/Posts/end_to_end_encryption_witn_blazor_wasm.html</link>
      <title>Roll Your Own End-to-End Encryption in Blazor WASM</title>
      <description>&lt;p&gt;Just the other day I released &lt;a href="https://ian.wold.guru/Posts/free_planning_poker.html"&gt;my free planning poker app&lt;/a&gt; and there was one thing missing that I really wanted to have: end-to-end encryption. Sure, folks aren't going to be entering sensitive information into a planning poker tool, right? Well, &lt;em&gt;probably&lt;/em&gt; not at least, right? Maybe it's more the principle of the matter, but I like that this application's server will never be in a position to even accidentally have that info.&lt;/p&gt;
&lt;p&gt;Collaborative applications which support users interacting in real-time are pretty common, and Blazor WASM seems to want to support this use case pretty well. Microsoft's documentation walks you through a real-time chat application, and there's no end of tutorials showing how ot use it with SignalR. I'll probably add to that noise in future. One thing that's unfortunately absent is encryption.&lt;/p&gt;
&lt;p&gt;Microsoft's own libraries are complicated and/or don't work on WASM (I'm still very unclear on the state of this - please comment if you know something), and I did not have any success with third-party libraries for Blazor. Do I really want to import a third party package for this though? These &amp;quot;small&amp;quot; Nuget packages are a dime a dozen around Blazor, and most of them are poorly maintained.&lt;/p&gt;
&lt;p&gt;After some tinkering this was an area I decided to roll my own, and I'm glad I did. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto"&gt;SubtleCrypto&lt;/a&gt; API supported by most browsers (all the important ones) is actually quite a good encryption provider, and it's easy to set up JS interop with Blazor. This solution clocks in at 58 lines of JS and 11 lines of C#, so I can copy and paste between projects and tailor however I need for each one.&lt;/p&gt;
&lt;h1&gt;The Simple Case&lt;/h1&gt;
&lt;p&gt;In my case, I only need to encrypt a few short strings. A title and a name - that's it! In fact, the solution I need is going to be almost exactly the same as &lt;a href="https://blog.excalidraw.com/end-to-end-encryption/"&gt;Excalidraw's encryption&lt;/a&gt;. In the case of my planning poker app, a user who creates a planning session will share their URL with their other participants, who can then join the session at that URL. That URL contains the session ID, and also the encryption key exactly as Excalidraw does.&lt;/p&gt;
&lt;p&gt;A note on the key, then: it is passed in the URL in the hash, so a URL for my application looks like &lt;code&gt;https://freeplanningpoker.io/session/2ae188d8#key=9aac428b962ead5a678b13f7&lt;/code&gt;. This is important, as the hash never makes it to the server.&lt;/p&gt;
&lt;p&gt;Because the key is stored in the URL, I can access it from the page's JS, so I don't really need to know about it from C# except when I first generate it, so that I can redirect the user to the correct session page. This can simplify my C# API down quite a bit:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;async Task&amp;lt;string&amp;gt; GetEncryptionKeyAsync();
async Task&amp;lt;string&amp;gt; EncryptAsync(string value);
async Task&amp;lt;string&amp;gt; DecryptAsync(string value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don't want to make things excessively complicated, especially for this case. Here then I want to write a bit of JavaScript with which these two methods can interop, and let the JavaScript worry about parsing the key from the URL and storing that.&lt;/p&gt;
&lt;h2&gt;Handling the Key&lt;/h2&gt;
&lt;p&gt;I encourage you to read the documentation on the SubtleCrypto API, it's quite straightforward. I'm going to use the AES-GCM algorithm, and we can use &lt;code&gt;window.crypto.subtle.generateKey&lt;/code&gt; for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;window.encryptionKey = await window.crypto.subtle.generateKey(
    { name: &amp;quot;AES-GCM&amp;quot;, length: 128 }, // The algorithm
    true,                             // The key is extractable - important!
    [&amp;quot;encrypt&amp;quot;, &amp;quot;decrypt&amp;quot;]            // This key is used for both directions
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that I'm storing the key at &lt;code&gt;window.encryptionKey&lt;/code&gt; - this is how I'm sorting the key on the JS side rather than the C# side. Not sure if putting it in &lt;code&gt;window&lt;/code&gt; is ideal, I tend to avoid JavaScript when I can. &lt;em&gt;insert skill issue meme here...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This creates a key object with a bunch of properties that SubtleCrypto uses, but we need to get a simple string to be able to pass through in our URL. For this purpose we can call &lt;code&gt;exportKey&lt;/code&gt; and encode it with &lt;code&gt;jwk&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;return (await window.crypto.subtle.exportKey(&amp;quot;jwk&amp;quot;, window.encryptionKey)).k
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can wrap these up into &lt;a href="https://gist.github.com/IanWold/8e337a22ce7eb386b65bb77948eebd66#file-encryption-js-L20"&gt;a &lt;code&gt;getEncryptionKey&lt;/code&gt; function&lt;/a&gt; and call this from C#, assuming we have injected &lt;code&gt;IJSRuntime jsRuntime&lt;/code&gt; into our class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public async Task&amp;lt;string&amp;gt; GetEncryptionKeyAsync() =&amp;gt;
    await jsRuntime.InvokeAsync&amp;lt;string&amp;gt;(&amp;quot;getEncryptionKey&amp;quot;) ?? /* Do something for null case */;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Getting the Key from the URL&lt;/h2&gt;
&lt;p&gt;When a new participant visits the link they were given by the session organizer, I need to get the key out of the URL and store it in &lt;code&gt;window.encryptionKey&lt;/code&gt;, where my encrypt and decrypt functions are going to expect it. When the window loads, it's simple to get the key out of the hash:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const objectKey = window.location.hash.slice(&amp;quot;#key=&amp;quot;.length);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And while there's a fair amount of fluff here, it's also simple to reconstruct the key (this comes right from Excalidraw's implementation):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;window.encryptionKey = await window.crypto.subtle.importKey(
    &amp;quot;jwk&amp;quot;,
    {
        k: objectKey,
        alg: &amp;quot;A128GCM&amp;quot;,                     // The algorithm
        ext: true,                          // Extractable
        key_ops: [&amp;quot;encrypt&amp;quot;, &amp;quot;decrypt&amp;quot;],    // For both operations
        kty: &amp;quot;oct&amp;quot;,                         // Not entirely sure...
    },
    { name: &amp;quot;AES-GCM&amp;quot;, length: 128 },       // Still the same algorithm
    true,                                   // Still extractable
    [&amp;quot;encrypt&amp;quot;, &amp;quot;decrypt&amp;quot;]                  // Still for both operations
); 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this can all be stuffed nicely into &lt;a href="https://gist.github.com/IanWold/8e337a22ce7eb386b65bb77948eebd66#file-encryption-js-L1"&gt;a DOMContentLoaded event handler&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Encrypting&lt;/h2&gt;
&lt;p&gt;Now the fun part! This is &lt;em&gt;almost&lt;/em&gt; as simple as calling &lt;code&gt;window.crypto.subtle.encrypt&lt;/code&gt;. As it happens, this &lt;code&gt;encrypt&lt;/code&gt; function expects to deal with &lt;code&gt;ArrayBuffer&lt;/code&gt; objects intead of strings. Fair enough, but we have to do a bit of wrapping around the whole deal. The first thing is to transform the string to an &lt;code&gt;ArrayBuffer&lt;/code&gt;, and that's easy enough with &lt;code&gt;TextEncoder&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const encodedValue = new TextEncoder().encode(stringValue);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can use this value and the encryption key that we've saved in order to encrypt the value:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const encrypted = await window.crypto.subtle.encrypt(
    { name: &amp;quot;AES-GCM&amp;quot;, iv: new Uint8Array(12) },        // Use empty IV
    window.encryptionKey,                               // The key we saved
    encodedValue
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have another &lt;code&gt;ArrayBuffer&lt;/code&gt; in &lt;code&gt;encrypted&lt;/code&gt;, but we want to translate that into a string before we send it up. For this, I'm going to use a method &lt;a href="https://davidmyers.dev/blog/a-practical-guide-to-the-web-cryptography-api"&gt;described by David Myers&lt;/a&gt; to get a base64 string from the &lt;code&gt;ArrayBuffer&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;return window.btoa(String.fromCharCode.apply(null, new Uint8Array(encrypted)));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In all, this makes for a very tidy, very simple &lt;a href="https://gist.github.com/IanWold/8e337a22ce7eb386b65bb77948eebd66#file-encryption-js-L32"&gt;encrypt function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And then another simple call from C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public async Task&amp;lt;string&amp;gt; EncryptAsync(string value) =&amp;gt;
    await jsRuntime.InvokeAsync&amp;lt;string&amp;gt;(&amp;quot;encrypt&amp;quot;, value) ?? /* Do something for null case */;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Decrypting&lt;/h2&gt;
&lt;p&gt;In this case now, I need to turn my base64 string &lt;em&gt;back&lt;/em&gt; into an array buffer, decrypt it, and then turn the resulting &lt;code&gt;ArrayBuffer&lt;/code&gt; back into a string to send up to the C#. The case of parsing the base64 string comes straight from David Myers again:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const bValue = window.atob(base64Value)
const buffer = new ArrayBuffer(bValue.length)
const bufferView = new Uint8Array(buffer)

for (let i = 0; i &amp;lt; bValue.length; i++) {
    bufferView[i] = bValue.charCodeAt(i)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's a little unfortunate but 6 lines isn't so terrible. The &lt;code&gt;buffer&lt;/code&gt; variable can now be passed into &lt;code&gt;window.crypto.subtle.decrypt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const decrypted = await window.crypto.subtle.decrypt(
    { name: &amp;quot;AES-GCM&amp;quot;, iv: new Uint8Array(12) },        // Same algorithm, empty IV
    window.encryptionKey,                               // The key we saved
    buffer
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And just as we initially used &lt;code&gt;TextEncoder&lt;/code&gt; to get the string from C# into an &lt;code&gt;ArrayBuffer&lt;/code&gt;, we'll use &lt;code&gt;TextDecoder&lt;/code&gt; to go the opposite direction:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;return new TextDecoder().decode(new Uint8Array(decrypted));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instantiating a new &lt;code&gt;Uint8Array&lt;/code&gt; turns the &lt;code&gt;ArrayBuffer&lt;/code&gt; into an array, which is what &lt;code&gt;TextDecoder&lt;/code&gt; wants.&lt;/p&gt;
&lt;p&gt;This is an equally simple invocation from C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public async Task&amp;lt;string&amp;gt; DecryptAsync(string value) =&amp;gt;
    await jsRuntime.InvokeAsync&amp;lt;string&amp;gt;(&amp;quot;decrypt&amp;quot;, value) ?? /* Do something for null case */;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;The More Complicated Cases&lt;/h1&gt;
&lt;p&gt;I'm really quite happy with the solution I have here for simple strings. More importantly, I'm a fan of small-code roll-your-own solutions because they give flexibility and customization. When I carry this code over to other projects, there might be (surely will be) different requirements that will require changes here. I want to demonstrate a couple of cases for which this code can easily be extended.&lt;/p&gt;
&lt;h2&gt;Encrypting Whole Objects&lt;/h2&gt;
&lt;p&gt;I struggle to think of a case where JSON serialization isn't the answer here. The good deal is that this can be achieved either in C# or JS.&lt;/p&gt;
&lt;p&gt;If you're handling serialization in C#, you can leave the JS as-is and add all the logic to &lt;code&gt;EncryptAsync&lt;/code&gt; and &lt;code&gt;DecryptAsync&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;using System.Text.Json;

public async Task&amp;lt;string&amp;gt; EncryptAsync&amp;lt;TValue&amp;gt;(TValue value)
{
    var serialized = JsonSerializer.Serialize(value);
    return await jsRuntime.InvokeAsync&amp;lt;string&amp;gt;(&amp;quot;encrypt&amp;quot;, serialized) ?? /* Do something for null case */;
}

public async Task&amp;lt;TValue&amp;gt; DecryptAsync&amp;lt;TValue&amp;gt;(string value)
{
    var serialized = await jsRuntime.InvokeAsync&amp;lt;string&amp;gt;(&amp;quot;decrypt&amp;quot;, value) ?? /* Do something for null case */;

    try
    {
        return await JsonSerializer.Deserialize&amp;lt;TValue&amp;gt;(serialized) ?? /* Do something for null case */;
    }
    catch
    {
        /* Do something for error case */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This seems like the best case to me as I'm only chaging one part of the code, but depending on your use case you might want to do the serialization in JS. In this case you still need some more error handling in C#.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;async function encrypt(value) {
    const stringValue = JSON.stringify(value);
    // Same as previous implementation
}

async function decrypt(value) {
    // Same as previous implementation

    return JSON.parse(decryptedStringValue);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IJSRuntime.InvokeAsync&lt;/code&gt; is generic, so we can cast right to the type we want, but we do need to handle errors. This does a JSON parse itself, so I'm not certain if this is more performant in most cases. That said, when we're doing the JSON serialization in JS, we don't need to modify our &lt;code&gt;EncryptAsync&lt;/code&gt; method from the first pass.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public async Task&amp;lt;TValue&amp;gt; DecryptAsync&amp;lt;TValue&amp;gt;(string value)
{
    try
    {
        return await jsRuntime.InvokeAsync&amp;lt;TValue&amp;gt;(&amp;quot;decrypt&amp;quot;, value) ?? /* Do something for null case */;
    }
    catch
    {
        /* Do something for error case */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using an IV&lt;/h2&gt;
&lt;p&gt;In my simple case, I did not use an IV (&lt;a href="https://en.wikipedia.org/wiki/Initialization_vector"&gt;initialization vector&lt;/a&gt;). Well, I should say that I used an IV with all zeroes. There are plenty of use cases for which you do want this though.&lt;/p&gt;
&lt;p&gt;Luckily, SubtleCrypto does have an easy way to generate these, but the catch is that we want to create a different IV per encryption, so it's no good to create a static IV at the same time that we create the encryption key. Therefore, the IV needs to ride alongside the encrypted value. An easy way to achieve this would be to generate the IV before encryption, the encrypt the value with this IV and create the base64 string, then create an object holding both this value and the IV, then serialize that object and base64 encode it. This would then allow the deserialization to extract the IV before deserializing the value.&lt;/p&gt;
&lt;p&gt;This is relatively straightforward to implement just in JS with &lt;code&gt;window.crypto.getRandomValues&lt;/code&gt;, assuming you don't care about the IV in C#:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;async function encrypt(value) {
    const iv = window.crypto.getRandomValues(new Uint8Array(12)); // Generate the IV
    const encrypted = window.btoa(String.fromCharCode.apply(null, new Uint8Array(
        await window.crypto.subtle.encrypt(
            { name: &amp;quot;AES-GCM&amp;quot;, iv: iv }, // Use the IV
            window.encryptionKey,
            new TextEncoder().encode(value)
        )
    )));

    const values = {
        value: encrypted,
        iv: iv
    };

    return window.btoa(JSON.stringify(values));
}

async function decrypt(value) {
    const values = JSON.parse(window.atob(value)); // Reverse the process

    const bValue = window.atob(values.value)       // Get the encrypted value
    const buffer = new ArrayBuffer(bValue.length)
    const bufferView = new Uint8Array(buffer)

    for (let i = 0; i &amp;lt; bValue.length; i++) {
        bufferView[i] = bValue.charCodeAt(i)
    }
    
    return new TextDecoder().decode(new Uint8Array(
        await window.crypto.subtle.decrypt(
            { name: &amp;quot;AES-GCM&amp;quot;, iv: values.iv },    // Use the new IV
            window.encryptionKey,
            buffer
        )
    ));
}
&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Wed, 17 Apr 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-04-17T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">farewell_twin_cities_code_camp</guid>
      <link>https://ian.wold.guru/Posts/farewell_twin_cities_code_camp.html</link>
      <title>Farewell, Twin Cities Code Camp</title>
      <description>&lt;p&gt;The &lt;a href="http://www.twincitiescodecamp.com"&gt;Twin Cities Code Camp&lt;/a&gt; was the first programming conference I attended. I was just a freshman in high school, yet it was invigorating to get to share time with such a large group of folks in this industry. I attended for several years, then stopped as I moved to Iowa City for university, then I attended again until COVID. At its peak they had hundreds of attendees, the conference spanned the entire weekend, and they were organizing both spring and fall events. It was as though the entire software engineering industry in the Twin Cities would turn up for these events.&lt;/p&gt;
&lt;p&gt;I learned an incredible amount at these conferences, and I give them a fair amount of credit for a lot of foundational ideas I hold. It was through a simple talk on the &lt;a href="https://github.com/sprache/Sprache"&gt;Sprache&lt;/a&gt; library that I ended up developing an interest in programming language theory. A simple talk on the then-new Windows Phone led me to write a lot of apps for the platform when it launched (famously Windows Phone now has a near-monopoly on mobile devices). It was my primary in-person source for learning about all the brand new emerging platforms, like GitHub!&lt;/p&gt;
&lt;p&gt;Above all I enjoyed the time I got to spend with people there, and the folks I was introduced to at those events. But, all good things come to an end, and understandably the organizers of the conference have decided to discontinue it. They have my gratitude for having organized it for as long as they did.&lt;/p&gt;
&lt;p&gt;So what now? Since COVID the &lt;em&gt;feeling&lt;/em&gt; of the industry seems to have gone through an entire shift; longstanding conferences seem to be falling by the wayside. I'm going to be looking into starting a local meetup group in downtown Minneapolis. &lt;a href="https://ian.wold.guru/about.html#connect"&gt;Get in touch with me&lt;/a&gt; if that sounds interesting. If you don't know what a &amp;quot;code camp&amp;quot; is - or if you are interested in organizing one - &lt;a href="https://learn.microsoft.com/en-us/archive/blogs/trobbins/the-code-camp-manifesto"&gt;read the code camp manifesto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Farewell, Twin Cities Code Camp!&lt;/p&gt;
</description>
      <pubDate>Wed, 27 Mar 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-03-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">four_deeply_ingrained_csharp_cliches</guid>
      <link>https://ian.wold.guru/Posts/four_deeply_ingrained_csharp_cliches.html</link>
      <title>Four Deeply-Ingrained C# Cliches</title>
      <description>&lt;p&gt;There's a lot to love about C# and .NET, and there are some things that I don't love as much. Then there are four bad habbits that are so deeply ingrained they've become cliches within our codebases. Despite being obviously bad practices, their ubiquity seems to force them into our codebases in spite of our knowing better. When fighting the battle against &lt;a href="https://grugbrain.dev/"&gt;complexity spirit demon&lt;/a&gt; it's easy to tire and allow these to slip through, perhaps justified by their disarming commonality.&lt;/p&gt;
&lt;p&gt;It's not as though these subjects haven't been written about before, but that they are still practiced in spite of our best efforts to craft well-written code. I think it's useful to call these four out, and I want to propose alternative solutions with which I have found success in the past. Take my solutions with a grain of salt if you will, but do have a think about a better alternative yourself. These cliches have plagued too many codebases, creating spaghettiable (is that a word?) code and exposing too many bug vectors.&lt;/p&gt;
&lt;h1&gt;Interfaces&lt;/h1&gt;
&lt;p&gt;Interfaces are critically overused &lt;em&gt;and&lt;/em&gt; misused. When I open a C# codebase I can guarantee I'll be treated to a whole host of interfaces that have exactly one implementation. Maybe they'll also be mocked in 200 different unit tests. I know I'll be treated to files that have classes so simple they haven't changed in five years, yet there's an interface right at the top for this class exposing the single public method. I know I'll find at least one (usually many) empty interfaces, and I'm always on the edge of my seat to find how much runtime reflection there is referencing the empty &lt;code&gt;IModel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What's going wrong here? I think we've forgotten the utility that interfaces have for us, yet interfaces are almost entirely necessary when developing even relatively simple production applications. Interfaces should be used to represent meaningful, coherent abstractions or capabilities relevant to your domain, and only used when there is a clear need for abstraction and/or polymorphism. One benefit gained by interfaces is to allow for multiple class implementations, facilitating class swapping, refactor testing, decorator pattern, or testing fakes. A (maybe better) benefit comes from the ability to assign multiple abstract concepts to a single concrete class, a la the Liskov principle.&lt;/p&gt;
&lt;p&gt;Often though we find a one-interface-per-class rule in C# codebases, which eliminates or outright ignores a fair set of benefits. How can any of us claim our C# code is SOLID if we restrict ourselves away from even being able to use the Liskov principle? Sure, we don't need to adhere to Liskov religiously, but it's a good heuristic to help judge when to use an interface. If they're only mirroring the class structure, they're unnecessary. In these cases the code is better without these interfaces. If you do need to swap the class in a future refactor, you can create an interface then; it's dead weight to live in the code in the meantime. If you need to rely on the interface for mocks or fakes for unit tests, there's a good chance you're testing wrong and you should think about that too.&lt;/p&gt;
&lt;p&gt;As an example, consider the following example which we have each encountered several thousand times:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public interface IMailChimpFacadeEmailService { ... }

public class MailChimpFacadeEmailService : IMailChimpFacadeEmailService { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there is almost never an &lt;code&gt;IEmailService&lt;/code&gt; to be found! In this case we're usually better just deleting &lt;code&gt;IMailChimpFacadeEmailService&lt;/code&gt;, but in the cases where the interface abstraction is necessary then replacing &lt;code&gt;IMailChimpFacadeEmailService&lt;/code&gt; with &lt;code&gt;IEmailService&lt;/code&gt; is entirely appropriate.&lt;/p&gt;
&lt;p&gt;Empty interfaces should be deleted outright. But how will we reflect on our models without an empty &lt;code&gt;IModel&lt;/code&gt; interface? Well, we probably shouldn't reflect on them, especially not after startup time. A lot of legitimate uses can be replaced by source generators here. These empty interfaces really grind my gears; they pollute my code and make me sad.&lt;/p&gt;
&lt;h1&gt;Abstractions&lt;/h1&gt;
&lt;p&gt;Overusing abstractions isn't a sin that's endemic to C# codebases specifically; it's across the entire industry. There's an interesting observation to be made that the incidence rate of overused abstractions tends to directly correspond with the number of software engineers involved in the team or company which owns that code - when we have time to kill at work, we tend to &lt;em&gt;work&lt;/em&gt;. Abstractions are inherently complex, and we should strive to create simple code. The trouble is that abstractions can increase the flexibility of code, so they're also essential tools. The prevailing wisdom is that abstractions should be used sparingly and when necessary, and nobody (not even me) can define when it's necessary to abstract.&lt;/p&gt;
&lt;p&gt;One common find in C# codebases is the passthrough layer - often the repository layer. Indeed, gone are the days of the Data Access Layer, we now have the Repository Layer, named after the pattern. This layer is often a passthrough to Entity Framework (EF, like most ORMs, already implements the repository pattern), to service layers which contact external services, or both. Whether it's a repository layer or not, passthrough layers are, I think universally agreed, incredibly annoying.&lt;/p&gt;
&lt;p&gt;These layers add no value, they don't provide modularity, and often hinder scalability. Demonstrably, they add to development time. Kill these layers, even if they cause you to have to rethink your architectural approach.&lt;/p&gt;
&lt;p&gt;Overall, I would say that an abstraction should clearly and demonstrably add distinct value or functionality. If you can't explain the value-add in one simple sentence, you've probably missed the mark. Architectural abstractions should specifically enhance modularity, scalability, or separation of concerns in a beneficial and articulable way. When refactoring (which ought be a constant practice), pay particular attention to abstractions. Celebrate when you can remove an abstraction, and always prefer clear, readable, imperative or procedural code over abstractions where the benefit of the latter is tenuous.&lt;/p&gt;
&lt;h1&gt;Exceptions&lt;/h1&gt;
&lt;p&gt;There's a lot that's been written about the ills of exceptions, and I think the negativity associated with them is maybe a bit exaggerated. One concept which I think is overlooked is the &lt;a href="https://joeduffyblog.com/2016/02/07/the-error-model/"&gt;two-pronged error model&lt;/a&gt;; that there are some errors which need to be handled immediately, and then others which require an interruption to the control flow. The fact of the matter is that exceptions satisfy the use cases for the latter - the truly &lt;em&gt;exceptional&lt;/em&gt; scenarios. We get into trouble however when we start relying on exceptions for all of our error handling.&lt;/p&gt;
&lt;p&gt;The blame for this one definitely falls first on the C# language - it really only natively supports exceptions and there's still not a great way to create and consume a robust result or option monad (discriminated unions when). That said, one of the variants of the result pattern are better for the former sort of error. If you can encapsulate the result of your operations - particularly your data access and business operations - in such a way that the resulting value can't be accessed without a success check, then you'll be guarding the consuming code against abusing your method - you'll be making your contract more explicit.&lt;/p&gt;
&lt;p&gt;Here's a simple result object for C# that uses the try pattern to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class Result&amp;lt;T&amp;gt;
{
    private bool isSuccess;
    private T? value;

    private Result(bool isSuccess, T? value)
    {
        _isSuccess = isSuccess
        _value = value;
    }

    public bool IsSuccess([NotNullWhen(true)]out T? result)
    {
        result = value;
        return isSuccess;
    }

    public static Result&amp;lt;T&amp;gt; Success(T value) =&amp;gt; new(true, value);

    public static Result&amp;lt;T&amp;gt; Failure() =&amp;gt; new(false, default);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can't consume the value unless I make a check now:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var itemResult = GetItem();
if (!itemResult.IsSuccess(out var item))
{
    // handle error case
    return;
}

item.DoSomething();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other bad aspect of exceptions comes when we start using them for situations that aren't even errors. It takes very little ingenuity - disappointingly little, even - to adapt exceptions as Malbolge's new &lt;code&gt;goto&lt;/code&gt;. It isn't uncommon to see this in codebases, especially in library or utility logic, where some non-happy-path, yet non-error, scenario will throw, expecting to be caught &lt;em&gt;somewhere&lt;/em&gt;. Sometimes this is code that merely lacks the most basic consideration (say, an email validator that throws if the string is empty instead of returning the validation error).&lt;/p&gt;
&lt;p&gt;Other times this is entirely intentional, given very careful consideration in the entirely wrong direction. A great example is the &lt;code&gt;IDataReader&lt;/code&gt; from &lt;code&gt;Microsoft.SqlClient&lt;/code&gt; - if I want to access a column that doesn't exist (i.e. &lt;code&gt;dataReader[&amp;quot;some_column&amp;quot;]&lt;/code&gt;) it throws! Maybe that would made sense if they implemented a &lt;code&gt;TryGetValue&lt;/code&gt; or &lt;code&gt;ContainsColumn&lt;/code&gt;, but they did neither. The problem is so onerous because the contract for this object is that I am &lt;em&gt;supposed&lt;/em&gt; to use try/catch here to poll for maybe-extant columns.&lt;/p&gt;
&lt;h1&gt;Unit Tests&lt;/h1&gt;
&lt;p&gt;I'll admit up front here that I'm a unit test hater. Always have been, and aggressively moreso every single time that I have to rewrite 20 unit tests because I just did a minor, non-behavior-altering refactor. Unit tests, as they're currently practiced, are in their ideal form a way to isolate a single method (or &amp;quot;unit&amp;quot;) of code to ensure its contract is respected. There's a lot wrong with this.&lt;/p&gt;
&lt;p&gt;First of all, our unit tests (almost) never live up to this. And it's not a question here of letting perfect be the enemy of good enough, they're so completely far away from this supposed ideal that I don't think it's fair to say they're even &lt;em&gt;trying&lt;/em&gt; to aspire to it. We find unit tests heavily reliant on mocks (or fakes if we're lucky) where methods that don't even make sense to test in isolation are stood up in an almost Frankensteinian manner to prod it with ill-figured test scenarios.&lt;/p&gt;
&lt;p&gt;On top of that, it's rare to find a test that even tests the properties of these methods appropriately. Often times, these tests don't even seem to know what the properties of the methods are they need to test for (a stark contrast to the much more focused discipline of &lt;a href="https://matthewtolman.com/article/what-is-property-testing"&gt;property testing&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;More troubling than either of those is that isolating methods is usually not the best way to test an application. The majority of methods in a business application do not need isolated testing. Rather, the system as a whole needs testing. This ideal of isolating methods to ensure that they adhere to the contracts they establish loses the forest for the trees; contracts can and ought be enforced in the code itself.&lt;/p&gt;
&lt;p&gt;This all culminates in the extremely common deception that our codebases are robust because we have 90% code coverage with thousands of tests that are severely abusing mocks and checking arbitrary input and output values, or call patterns worse yet! This leads to our codebases being so fragile that even small changes require several unnecessary changes across dozens of useless unit tests. And it's entirely missing the point to suggest that this is because unit tests aren't being used correctly. The definition I provided earlier is inherently flawed and leads towards code being developed this way. Mocks or fakes are necessary to set up most classes for testing, and this practice leads to huge swathes of tests which are coupled to the implementations they test.&lt;/p&gt;
&lt;p&gt;You'll be interested to learn then that this definition of a unit test that is so ubiquitous across our industry is entirely wrong. It's a great misunderstanding of the original intent of the &amp;quot;unit test&amp;quot;. I'll link &lt;a href="https://www.youtube.com/watch?v=EZ05e7EMOLM"&gt;this brilliant talk by Ian Cooper&lt;/a&gt; as an in-depth explanation, but it suffices to say that the original intent of the &amp;quot;unit&amp;quot; test was that a &amp;quot;unit&amp;quot; represented some portion of the system which was behaviorally isolated. Perhaps we should be easy on ourselves for having mistaken that to mean &amp;quot;method&amp;quot; but after enduring how many unit test refactors, my charitability is thin.&lt;/p&gt;
&lt;p&gt;Proper behavior tests are sorely underused, often not at all. It's quite typical that codebases will have maybe 95% unit testing and 5% proper behavior testing. This is backwards - when we're developing LOB or product software, the behavior is the most important, not the implementation. The tests &lt;em&gt;need&lt;/em&gt; to be divorced from the implementation in order to properly isolate and test behavior.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;tl;dr:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interfaces are overused and have become almost like header files, mindlessley mirroring the class structures in our codebases. Consider the Liskov principle when creating interfaces, and best yet only use interfaces when they're helping you fulfil a pattern or a meaningful abstraction.&lt;/li&gt;
&lt;li&gt;On the topic of abstractions, they tend to be used too frequently for cases which don't require abstraction. Sometimes it's difficult to see these abstractions enter the codebase over several PRs, so refactor your code frequently and aim to reduce abstractions every time. Prefer writing straightforward imperative or procedural code when possible.&lt;/li&gt;
&lt;li&gt;Exceptions are often used for any kind of error handling and sometimes as extra special &lt;code&gt;goto&lt;/code&gt;s. Only use exceptions for exceptional conditions that require you to break control flow, and consider using some form of the result pattern for error handling.&lt;/li&gt;
&lt;li&gt;Unit tests are the work of the devil; they tend to lead to tests coupled to the implementations they test. Most applications can (and should) be entirely tested with behavioral (integration, e2e) tests instead of unit tests. When you do need to isolate discrete methods or classes for testing, consider whether patterns like property testing would better test the behavior of the method.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These cliches lead to code of a lower quality. Sometimes they just make it a bit more tedious to add a feature or change a bit of business logic. Other times, often over time, they create mangled codebases where making progress is like swimming through molasses. Though these are common practices in C# codebases, they don't need to be. I've suggested several alternatives that I've found to be very well worth considering, but you might have your own alternatives instead. What's most important is that we can stop letting these four cliches into our code, and start doing &lt;em&gt;something&lt;/em&gt; more robust.&lt;/p&gt;
</description>
      <pubDate>Sun, 04 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-04T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">free_planning_poker</guid>
      <link>https://ian.wold.guru/Posts/free_planning_poker.html</link>
      <title>Thing I Made: FreePlanningPoker.io</title>
      <description>&lt;p&gt;For the last week and a bit I've been working on &lt;a href="https://freeplanningpoker.io"&gt;FreePlanningPoker.io&lt;/a&gt; and I think it's ready enough to show off. It's a Blazor app that uses SignalR and Redis to maintain state across several connected clients.&lt;/p&gt;
&lt;p&gt;I'm not an advocate for using points, and &lt;a href="https://ian.wold.guru/Posts/book_club_12-2023.html"&gt;I'm certainly no fan of scrum&lt;/a&gt; but the fact of the matter is that a lot of us get stuck in plenty of card pointing meetings. There's various tools to facilitate this online, and none of them had all the features I wanted, or they weren't &lt;em&gt;just right&lt;/em&gt;. Some of them incentivise practices that I think are detrimental. As many of us are prone to, I asked myself &amp;quot;How hard could it be to make one?&amp;quot;&lt;/p&gt;
&lt;p&gt;Very easy. Actually, extremely easy. Sure there's probalby a minor race condition or two but let's not let perfect be the enemy of good enough here. &lt;em&gt;(obligatory &amp;quot;/s&amp;quot;)&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;The Code&lt;/h1&gt;
&lt;p&gt;If you've worked with SignalR before, this is very straightforward. If you haven't, you're going to be up-to-speed in a few sentences. SignalR is Microsoft's websockets implementation for .NET. It works reasonably well, especially with ASP and Blazor. You're able to define interfaces for the server methods and the notifications the client will receive for a hub, then you can use source generators on the client to hook it up to the server. Though it's not &lt;em&gt;quiiiite&lt;/em&gt; as performant as I might appreciate at scale, it does the trick handily with a small amount of code.&lt;/p&gt;
&lt;p&gt;SignalR can use Redis as a backplane, and that's advantageous because this app is a sort of state machine without much data, so Redis is a good way to keep state on the server. Alas, there doesn't seem to be a good PostgreSQL backplane for SignalR otherwise &lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;I probably would have used that&lt;/a&gt;, but Redis is fast and lightweight for this sort of application.&lt;/p&gt;
&lt;p&gt;The code is open source, of course, and I've got &lt;a href="https://github.com/IanWold/PlanningPoker"&gt;much finer-grained descriptions&lt;/a&gt; of it in the readme.&lt;/p&gt;
&lt;h1&gt;Goals and Principles&lt;/h1&gt;
&lt;p&gt;My intial goal was met in about a week: I wanted a cheap app that solved my problem the way I wanted it solved. I'm very happy with that result! I want to keep this around though, I think it can be a benefit in a few ways. First, I think it's a good example of using the strongly-typed client source generators for SignalR in Blazor, which are to my knowledge not documented apart from &lt;a href="https://kristoffer-strube.dk/post/typed-signalr-clients-making-type-safe-real-time-communication-in-dotnet/"&gt;one post from Kristoffer Strube&lt;/a&gt;. In fact I think it's generally a good sample Blazor application.&lt;/p&gt;
&lt;p&gt;I think an application like this should be very minimal - not just in the UI and user experience, but it should take almost nothing to run the application. Sure, I chose Blazor which isn't exactly the most lightweight SPA framework, but overall the footprint is quite light. The server costs very little to run, being only an ASP server with a single SignalR hub and a Redis instance. It's got a low memory footprint and doesn't consume a lot of computing resources. Apart from resulting in a fast and maintainable application, this is a greener way to approach software.&lt;/p&gt;
&lt;p&gt;There should also be more software which encourages users to make it their own. I don't mean from a UI styling perspective, but from the code itself. If you want to take my code and deploy it yourself for you or your team to use on your own infrastructure, that should be easy, well-documented, and the code should provide all the environment variables you might need to tweak your setup. This application only really needs a Redis connection string but you get the idea. If you want to fork the code and make modifications on top of that, you should be well-supported by the code and its author in doing so.&lt;/p&gt;
&lt;p&gt;I chose the &lt;a href="https://en.wikipedia.org/wiki/Unlicense"&gt;Unlicense&lt;/a&gt; for this project for all those reasons too - more free code is more good.&lt;/p&gt;
&lt;p&gt;Finally, I think users' data should be respected. How much sensitive information is being transacted over a planning poker app? I hope none. Nonetheless, it's your information and that should be respected. This application deletes all the data from each session as soon as the session is left by the final participant. I don't log or retain any information from the sessions, and &lt;a href="https://github.com/IanWold/PlanningPoker/issues/8"&gt;I'm going to be adding client-side encryption&lt;/a&gt; to completely remove that as an issue.&lt;/p&gt;
&lt;h1&gt;Soliciting Feedback&lt;/h1&gt;
&lt;p&gt;If you use this, please do let me know if there's anything to improve, or if there's something you like about it. You can comment or &lt;a href="https://ian.wold.guru/Posts/ive_indiewebbed_my_site.html"&gt;webmention&lt;/a&gt; on this post, you can open an issue &lt;a href="https://github.com/IanWold/PlanningPoker/issues"&gt;on the repository&lt;/a&gt; or you can &lt;a href="https://ian.wold.guru/connect.html"&gt;connect with me&lt;/a&gt; through several other avenues.&lt;/p&gt;
&lt;p&gt;If you'd like to add something to the application, I'm happy to entertain PRs! I'd like to add some fun features to help distract the participants that they're in a planning meeting; that might do some more good for the world.&lt;/p&gt;
</description>
      <pubDate>Sat, 13 Apr 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-04-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">giscus_is_awesome</guid>
      <link>https://ian.wold.guru/Posts/giscus_is_awesome.html</link>
      <title>Giscus Is Awesome</title>
      <description>&lt;p&gt;&lt;a href="https://giscus.app/"&gt;giscus.app&lt;/a&gt; is really awesome!&lt;/p&gt;
&lt;p&gt;Last week I posted for the first time in six years and I figured I wanted to see about adding comments to this site. A Google search got me to Giscus really quick, and I was able to wire it up in just ten minutes. The thing that's still blowing my mind is that &lt;em&gt;it works&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Behind the scenes, it syncs up with the GitHub Discussions tab on the repo that hosts this website, and it matches a discussion to a page based on the the page's title.&lt;/p&gt;
&lt;p&gt;When somebody adds the first comment to a page, it creates a corresponding discussion thread for the page. When a page loads, it checks to see if there is a corresponding discussion and it loads the conversations from that discussion thread.&lt;/p&gt;
&lt;p&gt;I can do comment moderation and whatnot on GitHub discussions, and if somebody stumbles upon my website on GitHub they can see the conversation right there. If the tool stops working, the conversations still exist in GitHub, living right alongside the source for this site.&lt;/p&gt;
&lt;p&gt;And - I can't emphasis this enough - &lt;em&gt;it just works&lt;/em&gt;. I see so few tools that &lt;em&gt;just work&lt;/em&gt; and this one does.&lt;/p&gt;
&lt;p&gt;Check it out!&lt;/p&gt;
</description>
      <pubDate>Thu, 14 Sep 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-09-14T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">guerrila_devex_testing</guid>
      <link>https://ian.wold.guru/Posts/guerrila_devex_testing.html</link>
      <title>Guerrila DevEx Testing</title>
      <description>&lt;p&gt;I first became aware of hallway usability testing (also referred to as &amp;quot;guerrila&amp;quot; testing) at the beginning of my career when I was encouraged to read &lt;a href="https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/"&gt;The Joel Test&lt;/a&gt;, a now-famous article on what makes a good team. It's a brilliant article, and at more than two decades on I think it's still foundational as to how I think about the health of a team or a project. It contains the following description of &lt;code&gt;hallway usability test&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A hallway usability test is where you grab the next person that passes by in the hallway and force them to try to use the code you just wrote. If you do this to five people, you will learn 95% of what there is to learn about usability problems in your code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here, the phrase &amp;quot;use the code you just wrote&amp;quot; means you're going to &lt;em&gt;execute&lt;/em&gt; the code and ask the participant to use your actual software, navigating your UI and simulating a user. I love this phrasing though because that isn't where my mind went the first time I read that sentence. No, I instead thought it might be more interesting to yoink other engineers from the hallway and ask them to navigate my code. Not a code review, not some full architectural scrutiny, but a real-world test of whether my code actually works as &lt;em&gt;code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nowadays we have the term &amp;quot;developer experience&amp;quot; to refer to this concept of the developer-usability of a piece of code or a development environment or the like, though this is a notoriously hand-wavey term. Especially considering my code's quality, I'm not sure there's ever going to be a great objective way to measure that. At the broad level, different groups of engineers are going to be biased in different ways towards one pattern or practice or another. Different styles are better suited for different problems or types of applications, and even throughout the lifetime of a codebase we might find that changing styles might work better with a changing environment around the codebase.&lt;/p&gt;
&lt;p&gt;And yet it isn't nonsensical to talk about code quality or developer experience more broadly. While disagreeing on specifics, surely we can all say we've worked on codebases that were easy to modify or extend, as well as codebases that aren't. A few colleagues on the fringe might suggest - intending to argue that &amp;quot;developer experience&amp;quot; is either meaningless or not worth attention - that instead of good or bad codebases there's just varying levels of bad. Beware of this rhetorical play: it's still an admission that there do exist codebases of a higher quality than others! Developer experience is a finicky thing to capture but worth our consideration.&lt;/p&gt;
&lt;p&gt;So that raises the question as to how to consider developer experience. Can I measure it? Well, I can't quantify it - please comment below if &lt;em&gt;you&lt;/em&gt; can! However, when I write bad code, or at least code which my colleagues consider bad, they're usually quite ready to let me know. Giddy, sometimes, to point out my faults. Being fair, I do have a blog which allows me to anonymously gripe to the world - literally &lt;em&gt;tens&lt;/em&gt; of people on a good week - about &lt;em&gt;their&lt;/em&gt; bad code.&lt;/p&gt;
&lt;p&gt;This is how I gauge developer experience, and it's the basis for a lot of practices I've developed over the years. This is a terribly imprecise art, considering not just the subjectivity of the matter but also the large number of variables at play that can alter how we think about quality in code. That said, I defy you to come up with a better method than this: watch your colleagues &amp;quot;use&amp;quot; your code. Sit them down and watch them debug an issue or add a feature.&lt;/p&gt;
&lt;p&gt;This is the hallway devex test, and as I mentioned it is higher-level than a code review and lower-level that some fine-grained walk through a whole codebase. Code reviews are done frequently and focus narrowly on the bit of code being added or removed, while the latter exercise is so effortsome that I've only ever heard of such practice in theory. This test is the in-between: given a functioning codebase (one that's in production), how well does it align with our &lt;a href="https://www.infoq.com/articles/avoid-architecture-pitfalls/"&gt;quality attribute requirements&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Running this test is very easy. If your code is in production then you surely have a set of bugs or simple features you've identified which could be worked on. From your firm's hallway (or some sort of Slack roulette if you're remote) yoink a colleague and promise to give them coffee and/or doughnuts in 30 minutes (or an hour or whatever amount of time makes sense) under one condition: they have to work on a card you give them on your codebase. For what it's worth, the promise of a doughnut is typically unsatisfactory over Slack; in this case you can just promise to &lt;a href="https://buttondown.com/ianwold"&gt;subscribe to your colleague's book club&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The crucial step is to watch your colleague. Look for where the pain points are, look for the assumptions they make, and look at the expression on their face. If you ask them how they feel or what they think, you'll get absolutely nowhere. Humans are funny beings; we're bad at knowing how we feel and sometimes we're bad at knowing what we think. There's frequently a disconnect between a person's actions, beliefs, and perceptions. Crucially, there's often a disconnect about what we think or say about our own actions, beliefs, and perceptions, so you can't trust me to describe myself to you. Further, you can't directly observe my beliefs or perceptions. However, you can directly observe my actions &lt;em&gt;and you don't need to take my word for it&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;How I go about &lt;em&gt;doing software engineering&lt;/em&gt; on your code is what reveals my developer experience with your code. How your other colleague works reveals &lt;em&gt;their&lt;/em&gt; developer experience with your code. In a way this cuts down to exactly what &amp;quot;developer experience&amp;quot; is: you're observing the experience of your colleagues doing developering with your code. As Joel said of usability testing, just 5 hallway passersby will teach you 95% of what you need to know - I find this largely true for devex as well. This test is easy enough to run periodically, and from there you can develop a feedback loop of collecting pain points, addressing them, letting the codebase evolve a bit further, and repeat.&lt;/p&gt;
&lt;p&gt;What specifically you're looking to address is tough to universally quantify - the types of experiences working on an embedded system will be quite different from a CRUD webpage or a video game. You might even find your colleagues reveal whole new categories of pain points you hadn't previously considered! I don't think about this too much beforehand, but I can think of a few high-level questions I typically look to resolve:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Was it easy for my colleague to understand what the codebase is doing and how? Do they understand the architecture, patterns, conventions, and structures in the code?&lt;/li&gt;
&lt;li&gt;Was my colleague ultimately able to identify where to make the change or insertion?&lt;/li&gt;
&lt;li&gt;Did my colleague feel or become confident with the codebase? Did their confusion increase or decrease over time? Did unexpected side effects occur with their changes?&lt;/li&gt;
&lt;li&gt;What role did the development environment play - did they have difficulty working with the code in their IDE? More fundamentally, were they able to get it building right away?&lt;/li&gt;
&lt;li&gt;Where did they become confused? Where did they become frustrated? Where did they have to reach out to me? Can I identify styles, patterns, or such which they dislike?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In conclusion, I submit that &amp;quot;developer experience&amp;quot; is a useful subject for consideration, and in spite of its subjective nature I feel that I can say, definitively, that developer experience can be observed by observing the experience of developers. The hallway or &amp;quot;guerrila&amp;quot; test works for me to gauge this quality, and I'm sure it can work for you as well.&lt;/p&gt;
</description>
      <pubDate>Fri, 11 Oct 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-10-11T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">how_do_you_enforce_architectural_decisions</guid>
      <link>https://ian.wold.guru/Posts/how_do_you_enforce_architectural_decisions.html</link>
      <title>How do you Enforce Architectural Decisions?</title>
      <description>&lt;p&gt;&lt;em&gt;&amp;quot;My best-architected projects were the ones that never had an Architect.&amp;quot;&lt;/em&gt; I was recently told this by a former project manager who retired feeling unimpressed by colleagues over-eager to adopt the title &lt;em&gt;architect&lt;/em&gt;. I can't blame him, on this blog I've documented a number of observations I've made of flawed attitudes towards architecture. That is, attitudes about the nature of software architecture and the work that should be expected of architects, not ideas about this-or-that architectural pattern. Indeed, it seems intuitive that a project could be seriously derailed by an architect with a particularly nonproductive attitude about their job.&lt;/p&gt;
&lt;p&gt;This is the sort of architect my project manager friend seems to have found, and he told me how he stopped hiring architects: During interviews, he started asking one question to which he never received a satisfactory answer. With that, he refused to hire any more architects, and his projects were all the better for it. I happen to be very comfortable, though, professionally describing myself with the &lt;em&gt;architect&lt;/em&gt; title, so I'm curious to answer this question. &lt;em&gt;How do you enforce architectural decisions?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Imagine you've applied for an architectural position and you're asked this - what is &lt;em&gt;your&lt;/em&gt; answer? It's really not straightforward; the answer depends a great deal on your conception of the practice of architecture. I can imagine a lot of potential answers, and it's not hard to figure how one could go years without hearing a satisfactory one.&lt;/p&gt;
&lt;p&gt;For me, this is a trick question; at least, I'll answer it like it's a trick question. It's certainly a leading question by my reading, supposing that it's in the architects' nature to make and enforce decisions. This may be so, but I'd venture that most folks in our industry are going to adopt the model of the architect-as-superior; that architectural decisions are apart from and supercede engineering decisions. This is the trap.&lt;/p&gt;
&lt;p&gt;I think it sometimes does our industry a disservice that we lean on a construction analogy to describe our roles. In building a house, the to-be homeowner consults with an architect over floor plans and renders which result in a final blueprint passed to builders who assemble the final, tangible house. It's right to forgive anyone who mistakenly thinks that the analogy with our industry maps the homeowners to the business, builders to software engineers, and the architect to software architects. This is completely wrong though, and that this mistake has tangible implications for millions of software professionals and the projects we're meant to steward is a serious fault. In fact, the analogy would be to map the homeowner to everyone with a stake in the software's quality, the building architect to the software engineer, and the house builder to our compilers.&lt;/p&gt;
&lt;p&gt;Except for a very small number of cases, software is not developed by making a complete, abstract definition of the final product before writing all the code to fulfil the definition. Rather, software tends to be constantly created and recreated in an interactive process; typing is significantly less resource-intensive than constantly erecting and destroying walls and rooms and roofs. We iterate a bit, deploy that iteration for all stakeholders (clients/users are stakeholders too), they give us feedback, and we rejigger what we need for that new feedback. What part of the house-building process does that sound like? That's right, it's the consultation between the homeowner and the architect. Remember that from the analogy, the homeowner maps to the stakeholders, and the house architect maps to the software engineers. The code is the blueprint we're all consulting on, and it's trivially cheap to ask the house builders to build a brand new version of our blueprints - after all, the house builder maps to the compiler (which takes seconds to produce the built software).&lt;/p&gt;
&lt;p&gt;I will reiterate - it's a grave mistake to think that the software engineering process is the analogy-equivalent of the house builders building the blueprint. If you will, close your eyes and imagine a small office with a house architect and a couple commissioning her to build them a new home. There's a blueprint on the desk and a big, red button that says &amp;quot;BUILD.&amp;quot; The couple asks the house architect to make changes to the blueprint to accommodate some requirement they have, and the architect obliges and updates the blueprint. Let's imagine this office sits at the head of a long road, which we can look down through a window in the office. The road is lined with houses, each next house a bit different from the first, with addresses like &amp;quot;v1.0.101&amp;quot;, &amp;quot;v1.0.102&amp;quot;, and so on. The house architect hits her BUILD button, and within seconds a new house appears at the end of the row of houses - address &amp;quot;v1.0.103.&amp;quot; This process repeats ad infinitum. &lt;em&gt;This&lt;/em&gt; is what software engineering is, the process inside the office. Let's not let ourselves be distracted by any misdirections from the imperfection of the construction analogy.&lt;/p&gt;
&lt;p&gt;So where does that leave Mr. Software Architect? It might look like we've squeezed him out of the picture, but that's not true. Well, not entirely; I want to very intentionally exclude anyone who draws diagrams before any code is written and berates colleagues for not implementing his diagrams. Software architecture is not brought into existence by diagrams or charts or whitepapers or anything like that; these are just tools which can help us to explain and teach an architecture after it comes into existence. Software architecture is made by the very process of writing the software; the architecture emerges from the code that is actually written (and for aggregate systems it is begat by the components that are deployed and the interactions that emerge between them). Software architecture is an artifact of the engineering process - remember the office before the row of houses.&lt;/p&gt;
&lt;p&gt;If we're trying to think about software architecture as something abstract, separate from and superior to the code or the engineering process, we're completely missing the fundamentals. Software architecture might be better understood as subordinate to the engineering process, but really it's best understood as being &lt;em&gt;one of&lt;/em&gt; the essential components of what goes into doing software engineering. Every software engineer is making architectural decisions, every day. Every software engineer is, to a greater or lesser extent, a software architect. Not because they've developed a separate skill, but because software architecture is an aspect of doing software engineering.&lt;/p&gt;
&lt;p&gt;Where that leaves Mr. Software Architect then is that he is a sofware engineer all the same as his colleagues on his team. If we have somebody with the &lt;em&gt;architect&lt;/em&gt; title, what we ideally have is an engineer who has a particularly good understanding of the architecture aspect of engineering, somebody who strengthens the whole team by setting a quality example of thinking and engineering architecturally. Other folks on the team will have other strengths like design or security or performance, which are all themselves aspects of software engineering. How does our colleague who's good at performance lead the team to deliver a performant product? This seems like a silly question because the answer is obvious: if they're a professional then they'll act &lt;em&gt;professionally&lt;/em&gt; with the team on performance. All the same with our colleague who's good at architecture.&lt;/p&gt;
&lt;p&gt;There's no doubt that architectural decisions come to be made during the engineering process, and likewise there's no doubt that such decisions should inform future development. Nonetheless, I object to the question &lt;em&gt;how do you enforce architectural decisions&lt;/em&gt; because this question conjures an image of an individual architect making decisions and authoritatively holding them over an engineering team. If instead we want to have a conversation about how an architecturally-inclined member of the team can best contribute their skills, helping to find and bolster good architecture that comes out of the engineering process, then we can talk!&lt;/p&gt;
</description>
      <pubDate>Tue, 20 Jan 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-01-20T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">how_i_am_super_techy_in_my_gherkin</guid>
      <link>https://ian.wold.guru/Posts/how_i_am_super_techy_in_my_gherkin.html</link>
      <title>How I am Super Techy in my Gherkin</title>
      <description>&lt;p&gt;A few weeks ago I wrote about how &lt;a href="https://ian.wold.guru/Posts/its_okay_to_be_a_bit_techy_in_your_gherkin.html"&gt;it's okay to be a bit techy in your Gherkin&lt;/a&gt;, by which I mean that it's okay to include technical sorts of language in your Gherkin. Gherkin is designed to abstract away the specific technical details of automated tests to make it easy for engineers to collaborate with non-engineer stakeholders over automated tests for a codebase. The general guideline goes that Gherkin should be written in a &amp;quot;business-friendly&amp;quot; manner, but this is actually not quite right; rather, our Gherkin should be written in a &amp;quot;stakeholder-friendly&amp;quot; manner.&lt;/p&gt;
&lt;p&gt;If your stakeholders include very-technically-illiterate folks, then you'll define your Gherkin steps to account for that. If, on the other hand, your stakeholders all understand what it means to send an HTTP request into the service you're testing, the following is (almost certainly) going to be easier to collaborate over:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scenario: Create a resource when myFlag is on
    Given the feature flag &amp;quot;myFlag&amp;quot; is ON
    When I send a POST request to &amp;quot;/my/resource&amp;quot; with the body
        &amp;quot;&amp;quot;&amp;quot;
        {
            &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;,
            &amp;quot;age&amp;quot;: 42
        }
        &amp;quot;&amp;quot;&amp;quot;
    Then the response status code should be 200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since I wrote that last blog post, I've had a lot of successful collaboration over a couple projects at work, and I want to share some of my findings here. Ultimately, I'm impressed by the very tight testing loop which this &amp;quot;low-level&amp;quot; form of Gherkin supports for my team's backend services. If we observe a bug or find one while doing exploratory testing, then we'll already have the request body - this Gherkin format makes it very easy for anyone - not just engineers - to write a test which can be immediately executed on the codebase.&lt;/p&gt;
&lt;p&gt;So in a matter of minutes I have the issue reproducible on my local machine, and I can then reach out to other stakeholders to confirm the expected behavior. Again, everyone understands what an HTTP request is, so I can send them the Gherkin, and they can respond to me with updated, valid Gherkin in no time. This all shows the value in ensuring that you set up your Gherkin so that it works in the environment it's supposed to, &lt;em&gt;and&lt;/em&gt; that you're using it to actually facilitate collaboration.&lt;/p&gt;
&lt;h2&gt;Steps to Arrange Data&lt;/h2&gt;
&lt;p&gt;In my environment (this could be entirely different for you) anyone who understands what an HTTP request is knows, generally, how SQL or Redis or the like work. Setting up data for the test can be extremely simple that way. It's even easier for a document database. Take the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given the &amp;quot;items&amp;quot; table in the Postgres database has the following records:
    | id | name  | age |
    | 1  | 'Bob' | 42  |
    | 2  | 'Sue' | 50  |
And the &amp;quot;some&amp;quot; key in the Redis cache has the value &amp;quot;hello&amp;quot;
And the external endpoint &amp;quot;/my/service/other/resource/1&amp;quot; will respond with status code 200 and body
    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;id&amp;quot;: 1,
        &amp;quot;favorites&amp;quot;: [ &amp;quot;Bob&amp;quot;, &amp;quot;Sue&amp;quot; ]
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Database tables are set up with - surprise - tables, Redis values are set directly, and responses from external services can have their bodies written out in full. Redis is an interesting case actually, since it can have lists and hashes and so on. It's relatively simple to add those:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given the &amp;quot;some&amp;quot; key in the Redis cache has the list [ 1, 2, 3 ]
And the &amp;quot;other&amp;quot; key in the Redis cache has the hash
    | name   | value |
    | first  | 1     |
    | second | 2     |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To reiterate, this works so long as all collaborators understand those sorts of systems. When they do, this straightforward approach gives the path of least resistance in moving from an observed scenario into the Gherkin language which allows collaborating over what the business requirements (outputs from the service) are in these conditions. Speaking of which:&lt;/p&gt;
&lt;h2&gt;Steps to Test Responses&lt;/h2&gt;
&lt;p&gt;One key point is that a service (an HTTP API, in this case) does not &lt;em&gt;just&lt;/em&gt; output responses to requests. Yes, that's the primary thing to test, but you might want to ensure that events are enqueued in a Kafka or that specific logs are output in certain scenarios to drive alerting, or the like.&lt;/p&gt;
&lt;p&gt;These extra scenarios get a bit tricky and probably require that your tests have some way to read all of these output channels. That solution might vary wildly for your project, but the Gherkin steps can be very simple. Remember to keep the focus on what kinds of steps are both most understandable to all your stakeholders &lt;em&gt;and&lt;/em&gt; produce the least amount of friction when creating Gherkin scenarios from real-world observed scenarios.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Then an error level log is written containing &amp;quot;SuperImportantService failed to respond&amp;quot;
And the following event was sent to the Kafka topic &amp;quot;topic&amp;quot;
    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;id&amp;quot;: 1,
        &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing the actual response from the service is just as easy, though in this case we might want to validate more than just the body - response status code is quite meaningful over HTTP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Then the response status code is 200
And the response body is
    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;id&amp;quot;: 1,
        &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;,
        &amp;quot;favorites&amp;quot;: [ &amp;quot;Bob&amp;quot;, &amp;quot;Sue&amp;quot; ]
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, there is a catch here testing the bodies of both the event and the API response: how is the code written to match the bodies? If I write a multi-line string in my test but the server actually responds with a single-line JSON, I can't do a string compare. Even if we could solve perfectly for whitespace (I don't recommend trying) property rearranging is an issue. But then I also don't necessarily want to test &lt;em&gt;every&lt;/em&gt; property; maybe for some tests I just want to make sure one property is coming back altered.&lt;/p&gt;
&lt;p&gt;What's wanted then is to test that the response JSON is a superset (for lack of a better word - is there something else we call this?) of what we expect. Luckily, this is quite easy to define:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;bool IsJsonSuperset(JsonElement targetJson, JsonElement expectedJson) =&amp;gt;
    expectedJson.ValueKind switch
    {
        JsonValueKind.Object =&amp;gt;
            expectedJson.EnumerateObject().All(expectedProperty =&amp;gt;
                targetJson.TryGetProperty(expectedProperty.Name, out var targetValue)
                &amp;amp;&amp;amp; IsJsonSuperset(targetValue, expectedProperty.Value)
            ),
        JsonValueKind.Array =&amp;gt;
            targetJson.GetArrayLength() &amp;gt;= expectedJson.GetArrayLength()
            &amp;amp;&amp;amp; expectedJson.EnumerateArray().All(expectedArrayElement =&amp;gt;
                targetJson.EnumerateArray().Any(targetArrayElement =&amp;gt; IsJsonSuperset(targetArrayElement, expectedArrayElement))
            ),
        _ =&amp;gt; targetJson.GetRawText() == expectedJson.GetRawText()
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Accounting for HTTP Headers&lt;/h2&gt;
&lt;p&gt;Ah, headers are just a table - surely I could just include them along with the body when I send the request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When I send a POST request to &amp;quot;/my/resource&amp;quot; with header and body
    | name          | value            |
    | Content-Type  | application/json |
    | Authorization | Bearer hello     |

    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;,
        &amp;quot;age&amp;quot;: 42
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alas, Gherkin does not support such an idea. In a traditional setup, we'd break this down into multiple steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given the request has header &amp;quot;Content-Type&amp;quot; with value &amp;quot;application/json&amp;quot;
And the request has header &amp;quot;Authorization&amp;quot; with value &amp;quot;Bearer hello&amp;quot;
When I send the request ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But barrier! My goal is to make it as easy and seamless as possible to move from scenarios we observe in test and prod into our Gherkin - and I want &lt;em&gt;anyone&lt;/em&gt; to be able to do that. We need more simple.&lt;/p&gt;
&lt;p&gt;How this shakes out in any particular circumstance is going to be affected by tooling and preferences and the usual factors, but what I've found works is to adopt the &lt;a href="https://daily-dev-tips.com/posts/what-exactly-is-frontmatter/"&gt;frontmatter&lt;/a&gt; concept to the JSON bodies. With my specific projects (and I can't stress enough that this may be drastically different depending on a lot of factors) the easiest thing has been to include the headers as YAML frontmatter atop the JSON in the body:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When I send a POST request to &amp;quot;/my/resource&amp;quot; with the body
    &amp;quot;&amp;quot;&amp;quot;
    ---
      headers:
        - Content-Type: application/json
          Authorization: Bearer hello
    ---
    {
        &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;,
        &amp;quot;age&amp;quot;: 42
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's easy then to parse out &lt;code&gt;---&lt;/code&gt; and get the information from the YAML. That &amp;quot;frontmatter&amp;quot; could just as easily be JSON, or you could even abandon the pseudo-frontmatter idea and write the whole body using &lt;a href="https://timdeschryver.dev/bits/http-files"&gt;http file syntax&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sat, 08 Mar 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-03-08T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">how_to_write_an_app_in_one_day</guid>
      <link>https://ian.wold.guru/Posts/how_to_write_an_app_in_one_day.html</link>
      <title>How to Write an App in One Day</title>
      <description>&lt;p&gt;I've been pretty short on time in the last few months; my recent rate of output on this blog might attest to that. Between learning the ropes at a new role at work and a large number of personal engagements, I haven't had a lot of time to get a lot of coding in.&lt;/p&gt;
&lt;p&gt;Nonetheless, I've gotten a number of projects up and going. I previously wrote about &lt;a href="https://ian.wold.guru/Posts/successful_personal_projects.html"&gt;what makes a successful personal project&lt;/a&gt;, but I'm thinking now I left a lot out: what is there to be done in the face of serious time constraints? Or, potentially more interesting, are these time constraints a benefit? I think I can give some good thoughts on the former question, at least.&lt;/p&gt;
&lt;p&gt;I've found that you can get a lot done in a single day. You can even get a lot done in a few hours in the morning in a cafe; I'm writing this now from a quiet cafe on a rainy day (this is a productivity cheat code, at least for me). Building something meaningful in this time frame just reduces down to being organized: set a reasonable goal, know what that goal is, implement realistic steps to getting it done, pay for your coffee, and you're now the proud owner of a whatever you just made!&lt;/p&gt;
&lt;p&gt;I guess this is just a more practical elaboration on my personal projects article...&lt;/p&gt;
&lt;h2&gt;Rome wasn't built in a day but &amp;quot;todo&amp;quot; apps can be built in a couple hours&lt;/h2&gt;
&lt;p&gt;Most of the projects I have in incomplete states are there not because I didn't set a realistic goal for something to build but because I didn't understand well-enough what I wanted to build. I don't think it's reasonable to expect specific definitions to rise out of an active coding session; if you don't know what you want to code then writing code is putting the cart before the horse.&lt;/p&gt;
&lt;p&gt;This is maybe homework to do before sitting down then, but the advantage is it can be done over several days during the free moments we get for thinking. It pays to be more thorough in this thinking than less - what are all the models I need to handle, what user actions are there, what's the service architecture going to look like, how do the user actions inform the code architecture, what technologies do I know that will enable me to implement this, what am I lacking in knowledge that's going to be a blocker?&lt;/p&gt;
&lt;p&gt;To boot, these questions don't just need answers but reasonable answers. You're not going to build your cool app if you start in a language you've never seen before. It's perfectly reasonable to have a project in a brand new language, but this article is focusing on trying to maximize &lt;em&gt;how much thing&lt;/em&gt; can be built in a short time.&lt;/p&gt;
&lt;p&gt;If you know your service architecture, user actions, and models pretty well then you're 80% of the way there, and that's usually where I start. It's good to know your knowns and unknowns, and it's good to know the specific technologies you'll need to employ to get the thing off the ground. To be very thorough, it's great to think through all the extra tidbits that tempt us on the way: does the project really need a full OTEL dashboard, do I really need a revproxy, does a side project really need automated tests, and so on (the answer should probably be &amp;quot;no&amp;quot; for most of these).&lt;/p&gt;
&lt;h2&gt;Just use the thing you know, and use it well&lt;/h2&gt;
&lt;p&gt;To reiterate, this is from the mindset of wanting to focus on delivering the thing; I don't want to be taken as advocating for never learning new bits. I have a grab bag of technologies, providers, and libraries that I reach for when I need something that isn't going to be a blocker. When I need a database &lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;I just use Postgres&lt;/a&gt;, I almost exclusively use C# or Go, I do exclusively use GitHub and &lt;a href="https://railway.com/"&gt;Railway&lt;/a&gt; to build and deploy. I like &lt;a href="https://clerk.com/"&gt;Clerk&lt;/a&gt; for auth, &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; for payments, and &lt;a href="https://nats.io/"&gt;NATS&lt;/a&gt; as a message bus.&lt;/p&gt;
&lt;p&gt;You'll have a different grab bag than I do, to be sure. The point is that these are things that I &lt;em&gt;know&lt;/em&gt;, I don't have to spend time wondering how to do &lt;em&gt;this&lt;/em&gt; or &lt;em&gt;that&lt;/em&gt; as I'm building an application. If I want to deliver the thing, I want to minimize the number of things I don't know, and focus my energy on tackling those things. If you think about it, the amount of time required to deliver a project is a sum of two numbers: the time required to get through the setup for all the things I know, and the time required to think through, experiment, and debug the things I don't know. The size and complexity of your app influence the first number, and the number of unknowns influences the second number; both potentially exponentially.&lt;/p&gt;
&lt;p&gt;What's just as important as choosing the right tools is using them correctly. If you're reading my blog, you should know the software development loop: 1. make it work, 2. make it work right, 3. make it pretty. This loop should be as tight as possible, you should &lt;em&gt;not&lt;/em&gt; skip step 3, and you should start with the most difficult and necessary things.&lt;/p&gt;
&lt;p&gt;Let me elaborate on that. When we start a project, we're probably going to have some kind of startup template for whatever language/library we've chosen. Maybe too frequently, we see that run locally and then we dive into sketching out the UI or server calls. This is not just disrespecting the loop, it's also not tackling the hardest problem first! We've done step 1, but no doubt the standard app templates aren't &lt;em&gt;quite&lt;/em&gt; right for you; you'll need to change some low-level things and clean up the code to your preference. Do that first!&lt;/p&gt;
&lt;p&gt;Then, what's the next hardest thing in setup? Yes, setup is generally easy, but what's the next hardest thing? I find most bugs come from deploying the app on the cloud if you're doing a website, or setting up the installer if it's a desktop app, or whatever deployment deal for whatever other thing. If I'm setting up a web app my next step is to get a GitHub repo and deploy it on Railway, and I'll even just start with the server at that. Invariably there's something that's a different version or been improved since I did this last month that needs to be ironed out. Get it working right, then go over it all again to make sure it's pretty. Keep the work area clean and you can go much faster.&lt;/p&gt;
&lt;p&gt;Then when I add my Postgres or Clerk auth or whatever else I need to set up, I can see it work locally &lt;em&gt;and&lt;/em&gt; right away I'll validate that it works when deployed. Sooner than later I'll also deploy a production version (unless that deployment &lt;em&gt;is&lt;/em&gt; the production version; that's a great time-saver). Particularly if you have third-party auth, separate environments can be a little hairy. After we're set up, what are the unknowns we've identified? These are the next hardest things.&lt;/p&gt;
&lt;p&gt;By always tackling the difficult things up front you're always keeping your time use most efficient. Practicing the development loop tightly means that at every step you're not just marching towards the finish line, but you're resolving error states, edge cases, code quality, and keeping the UI looking right. You're not deferring little things to the end of the project because those little things add up (note here: this is how you avoid the 80/20 trap). The development loop means we're not exploding our backlog, our app will be done when we're finished working through our user actions.&lt;/p&gt;
&lt;h2&gt;And don't forget to pay for your coffee&lt;/h2&gt;
&lt;p&gt;I like working from the cafe near my house, that's a productive spot for me. Environment can be such a huge factor in our ability to function, not just at work or when writing an app but always. If you understand the environment that makes you happy you'll find it's much easier to sort through a project.&lt;/p&gt;
&lt;p&gt;I suppose &amp;quot;do what makes you happy&amp;quot; is just obvious life advice though?&lt;/p&gt;
</description>
      <pubDate>Sat, 25 Oct 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-10-25T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">intuiting_jevons_paradox</guid>
      <link>https://ian.wold.guru/Posts/intuiting_jevons_paradox.html</link>
      <title>Intuiting Jevon's Paradox</title>
      <description>&lt;p&gt;I &lt;a href="https://ian.wold.guru/Posts/three_laws.html"&gt;wrote in a recent post&lt;/a&gt; about Jevon's Paradox, and I received some feedback on that; more than I usually do. As always, I'm grateful when you all &lt;a href="https://ian.wold.guru/connect.html"&gt;contact me&lt;/a&gt; though continue to encourage use of the &lt;a href="https://ian.wold.guru/Posts/giscus_is_awesome.html"&gt;excellent comment feature&lt;/a&gt; or, if you want to be one of the cool kids, &lt;a href="https://ian.wold.guru/Posts/ive_indiewebbed_my_site.html"&gt;webmentions&lt;/a&gt;, both of which I recently included on this blog! Anywho, I want to expand on this topic.&lt;/p&gt;
&lt;p&gt;Jevon's Paradox is one of those very unintuitive but easily observable facts of life. It was originally descried by economists (so I'm lead to believe) but has applications across the board, economics being the strage confluence of so many different social studies as it is. There are many different staements of the paradox, but I like this one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As the efficiency of consuming a resource is increased, the net consumption of that resource increases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's a bit of a compact definition, so it's worth giving a more plain-language explanation. It's not a paradox in the logical sense (i.e. &lt;a href="https://en.wikipedia.org/wiki/Russell%27s_paradox"&gt;Russell's Paradox&lt;/a&gt;), but in that its implication subverts our expectations. By way of example, I can explain it using the well-known concept of &lt;a href="https://en.wikipedia.org/wiki/Induced_demand"&gt;induced demand&lt;/a&gt; in urban planning. Suppose we live in a city with a highway going through it and we've been tasked by the mayor with finding a solution to that it always seems to be in a traffic jam. The most straightforward and intuitive response would be to add more lanes to the highway, right?&lt;/p&gt;
&lt;p&gt;In fact, if you clicked that link describing induced demand, you'd know that adding more lanes is not the right answer, as it will actually cause an even greater demand on the highway than there was before. What's more, this is not a linear demand: if the current demand for the highway is x cars per lane per hour, after adding more lanes it's actually xy cars per lane per hour, where y is some constant greater than one such that the resulting demand is even more intensive after adding more lanes. This is the &amp;quot;paradox&amp;quot; bit - it's entirely unintuitive that increasing the units of highway resource (lanes in this case) should result in an even higher demand per resource unit. We expect demand to remain the same, and that increasing the efficiency of use will resolve issues in congestion caused by that use. Because of Jevon's Paradox, however, not only have we failed to resolve the congestion issue but we've even made it worse.&lt;/p&gt;
&lt;p&gt;What of these implications then? There are a lot of conclusions to draw. First, and I think most importantly, we can say that when there are congestion issues with the consumption of a resource, making that consumption easier will exacerbate the problem and should not be considered as a solution. What solutions there are to the problem are probably context-specific, but if it involves increasing consumption efficiency then that's not one.&lt;/p&gt;
&lt;p&gt;The next conclusion we can draw is that the inverse of Jevon's is probably true:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As the efficiency of consuming a resource is decreased, the net consumption of that resource decreases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If I remove lanes from the highway, then of course there won't be as many cars on the highway. To extend this example, I might ask if removing lanes has resolved any issues though? There is some level of need to consume road resources, so unless I can find the root cause of &lt;em&gt;that&lt;/em&gt; need I'm probably just shifting the problem around by changing the highways. This is another important conclusion from Jevon's, albeit more indirect than the others: address problems at their root. It seems that &lt;a href="https://en.wikipedia.org/wiki/Five_whys"&gt;the five whys&lt;/a&gt; end up in a lot of places!&lt;/p&gt;
&lt;p&gt;Jevon's Paradox is so called because it's unintuitive, and indeed without a proper intuition for its implications you might find yourself amid the problem it describes. Certainly we're not all urban planners dictating the adding or removing of highway lanes, but indeed it has plenty of applications for us software engineers. Any software is necessarily a resource user - there's computational, storage, and networking resources which are easy for us to understand. The ease of consuming each has only increased over time, but is our software any better? In a lot of ways software has become much less efficient, bloated, worse for the users, and slower. Jonathan Blow &lt;a href="https://www.youtube.com/watch?v=FeAMiBKi_EM"&gt;has documented this quite well&lt;/a&gt; I think. Are we Jevon's-ing ourselves and our software?&lt;/p&gt;
&lt;p&gt;Maybe. I'm certain a collective failure to intuit Jevon's is not the only problem with our industy, and I'm doubtful that it might even be a major factor. However, I find it quite compelling to think in terms of resource consumption, and Jevon's is one of the prime gotchas for the ways in which software could be said to be a resource consumer.&lt;/p&gt;
&lt;p&gt;How to intuit it? The answer is common to many problems in software engineering: I think you need to think about it really hard. This is also my answer when colleagues ask how I know my code works. Being infallible as I am, this of course proves to be correct every time and I have never had an instance of being wrong on this count! To be serious though, this is one to keep in the back of the mind. When approaching architectural questions, ask whether you're solving a resource problem in the wrong way, or if you're creating a potential one. Consider whether Jevon's applies, and if you're diagnosing an existing resource problem consider whether it &lt;em&gt;had&lt;/em&gt; applied.&lt;/p&gt;
</description>
      <pubDate>Fri, 13 Dec 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-12-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">its_better_to_be_consistently_incorrect_than_consistently_correct</guid>
      <link>https://ian.wold.guru/Posts/its_better_to_be_consistently_incorrect_than_consistently_correct.html</link>
      <title>It's Better to be Consistently Incorrect than Inconsistently Correct</title>
      <description>&lt;p&gt;Anyone who has worked with me for any period of time is probably tired of hearing me say this. &amp;quot;It's better to be consistently incorrect than inconsistently correct.&amp;quot; I don't say this to mean that being inconsistent is a good thing; rather, I say this whenever I need to underscore the importance of consistency, almost always with respect to a piece of code. Codebases with established conventions - which consistently adhere to these conventions - are, in my opinion and experience, easier to comprehend and maintain.&lt;/p&gt;
&lt;p&gt;If I'm working in an app that uses the pipeline pattern to chain behaviors everywhere but then throws a random decorator pattern at me for one slice, I'm going to be confused. Maybe scared. Probably both. Either way, I'm paying a lot of attention to that area, and usually for naught. Suppose this hypothetical app has a 2-tier architecture which might genuinely be served better by decorators, as opposed to pipelines, for its use case: the engineer who checked in the decorator for the one slice probably had good reasons for doing so; it very well could be the &amp;quot;correct&amp;quot; solution for this app.&lt;/p&gt;
&lt;p&gt;But this is where I come in with my annoying catchphrase. The app has been established as using pipelines, and pipelines are the consistent answer here. Throwing decorators in instead breaks this consistency, irrespective of the correctness of that solution. Indeed, in a lot of cases these two patterns accomplish the same thing, and the benefits gained by using one over the other are typically minimal. This is where it's more important to adhere to consistency. When I'm in this codebase, I know that I'm going to be treated to a big helping of pipeline. It's better to be consistently incorrect than inconsistently correct.&lt;/p&gt;
&lt;p&gt;What makes code - especially code &lt;em&gt;style&lt;/em&gt; - correct or incorrect? The more fine-grained of &lt;em&gt;thing&lt;/em&gt; we want to call &amp;quot;correct&amp;quot; or &amp;quot;incorrect&amp;quot;, the faster this distinction becomes a matter of personal preference. Pipeline vs decorator pattern is, in simple codebases, almost always a matter of preference. Maybe your debugging tools favor pipelines, maybe you'll write less code with decorators. If we're talking about a simple enough app, the distinction does not become so important as to rise above the level of consistency - pick one and be consistent. If the weight is so far in the favor of one vs the other &lt;em&gt;and&lt;/em&gt; the weight comes down to the opposite one used, then you know that what you need is a refactor, not a fractured codebase.&lt;/p&gt;
&lt;p&gt;Maybe instead I should say &amp;quot;it's better to consistently adhere to established principles in a codebase in spite of your subjective feelings towards them than inconsistently applying your subjective feelings across the codebase,&amp;quot; but that's really a mouthful. Maybe I should say &amp;quot;I don't care about your feelings, consistent patterns are measurable and predictable,&amp;quot; but that comes off as uncaring. I do care about your ideas, I just don't want them making my codebase more difficult to navigate. It's better to be consistently incorrect than inconsistently correct.&lt;/p&gt;
&lt;p&gt;This conversation inevitably brings up the debate over the utility of consistency. I think there's strong examples of universal agreement for situations where consistency is highly valued - we tend to hold naming conventions as being important for a codebase, and using a single build system for projects (in the same language) within the same repo tends to be important. Are there examples of (near) universal agreement among software engineers of consistency being unwelcome? I can think of a few examples that aren't universally held - such as individuals who feel stifled in their creativity in a certain codebase, or when it's annoying that a codebase uses a style or pattern which is perceived as &amp;quot;incorrect&amp;quot;. Ah!&lt;/p&gt;
&lt;p&gt;These are the situations though that I suggest consistency is more valuable; these are the situations where the value judgement is subjective. What is &amp;quot;incorrect&amp;quot; is subjective, but what is &amp;quot;consistent&amp;quot; is usually measurable. If it's not perceivable, measurable, and obvious then it isn't consistency!&lt;/p&gt;
&lt;p&gt;Now here's one situation that's important to consider: when the &lt;em&gt;consistent&lt;/em&gt; thing to do is objectively incapable of satisfying the requirement of the piece of code. There's another saying, not my own, which goes nicely along with the one that I'm explaining here: &amp;quot;Things that behave the same should look the same, and things that behave different should look different.&amp;quot; I think this is hand-in-hand with my idea about consistency. If one piece of code behaves fundamentally different from another piece of code, it shouldn't look similar. The requirement for consistency does not cross this boundary. If we have a layer in our application where each class persists some data on a single database, those should all probably look and act pretty consistently. If we add a layer of classes that contact external services to perform side-effects in the system, those classes should probably look and act a bit different to the persistence layer!&lt;/p&gt;
&lt;p&gt;To take it back to the center though, we all perceive different things as being &amp;quot;correct&amp;quot; or &amp;quot;incorrect&amp;quot; in our code. Objectively incorrect things are bugs and unserved requirements; we fix those. Most of our disagreements are over subjective ideas. We can be tempted to check in code - one class here, one method here - that imposes our subjective ideas of correctness on an unwilling codebase. We should avoid doing this; consistency in style and pattern is an important quality of our codebases.&lt;/p&gt;
&lt;p&gt;It's better to be consistently incorrect than inconsistently correct.&lt;/p&gt;
</description>
      <pubDate>Thu, 15 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-15T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">its_okay_to_be_a_bit_techy_in_your_gherkin</guid>
      <link>https://ian.wold.guru/Posts/its_okay_to_be_a_bit_techy_in_your_gherkin.html</link>
      <title>It's Okay to be a Bit Techy in Your Gherkin</title>
      <description>&lt;p&gt;No, the title is not an innuendo, I mean tests written in &lt;a href="https://www.functionize.com/blog/what-is-gherkin-how-do-you-write-gherkin-tests"&gt;Gherkin&lt;/a&gt;, the language developed to facilitate better &lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development"&gt;BDD&lt;/a&gt; practices. Gherkin provides an abstraction between the definition of a test and the code which executes the test, allowing non-coding stakeholders to collaborate with engineers over automated tests for a project. While it's not the only way to &lt;a href="https://en.wikipedia.org/wiki/Shift-left_testing"&gt;shift testing left&lt;/a&gt; in the development cycle, I've used it to great effect on professional projects over the last few years that I've been at Crate and Barrel.&lt;/p&gt;
&lt;p&gt;Gherkin is meant to be written in as human-readable a style (read: &lt;em&gt;non-engineer style&lt;/em&gt;) as possible, this being what can facilitate collaboration between all the various sorts of stakeholders. If you've used Gherkin for a period of time as I have, you'll have encountered the suggestion that Gherkin tests should have &lt;em&gt;no&lt;/em&gt; technical ontology, keeping its abstractions entirely separate from its test subject. Indeed, a proper distinction between a bit of code and its tests is necessary, but I want to push back on the idea that there shouldn't be any techyness in the Gherkin tests.&lt;/p&gt;
&lt;p&gt;Indeed, for plenty of cases it does make more sense to allow the Gherkin tests to be more techy. I don't know that I can provide a formal definition of the word &amp;quot;techy&amp;quot; for my purposes here, but I think I can give a fair example: suppose I want to test a login page. The techy way might make use of identifiers and tech-focused concepts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given the user navigates to &amp;quot;http://example.com/login&amp;quot; using a Chrome browser
When the user inputs &amp;quot;john_doe&amp;quot; into the element with id &amp;quot;username-field&amp;quot;
And the user enters &amp;quot;password123&amp;quot; into the element with id &amp;quot;password-field&amp;quot;
And clicks the button with id &amp;quot;login-button&amp;quot;
Then the element with id &amp;quot;welcome-banner&amp;quot; should display &amp;quot;Welcome, John Doe!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the non-techy way would abstract those away to only describe user behavior:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given John Doe is on the login page
When he enters his username and password
And clicks the &amp;quot;Login&amp;quot; button
Then he should see a welcome message saying &amp;quot;Welcome, John Doe!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These styles are two extremes. Identifiers really don't need to be used in such a case, but on the other hand I could feasibly have different business rules for different browsers. Certainly it should be no trouble to explicitly state user input in the tests. Maybe a better middle ground would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Given John Doe is on the login page using a Chrome browser
When he enters username &amp;quot;john_doe&amp;quot; and password &amp;quot;password123&amp;quot;
And clicks the &amp;quot;Login&amp;quot; button
Then he should see a welcome message saying &amp;quot;Welcome, John Doe!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you were to take a dogmatic, anti-techy view regarding your Gherkin, you might feel that this is a bit too much techyness. I don't think most would see it that way - certainly not most stakeholders. This is exactly the point I mean to make: the stakeholders with whom we are collaborating are stakeholders on a software project; they &lt;em&gt;are techy&lt;/em&gt; to &lt;em&gt;some level&lt;/em&gt;. Lesser than the engineers one would indeed hope, but clearly not incapable of navigating the tech world. In fact, I'll suggest that software tests do in fact need to be technical in plenty of ways, no matter the level of abstraction. They are, after all, automated tests for software!&lt;/p&gt;
&lt;p&gt;To develop the idea a step further, I think I can give a good example of a time to have very-techy Gherkin. There's no doubt that the way stakeholders collaborate over a project is greatly - maybe mostly - influenced by &lt;em&gt;who&lt;/em&gt; the stakeholders are and what the project is. The same would be true for any Gherkin tests used for the project - their nature will be mostly influenced by the nature of the stakeholders and project. It follows that if the stakeholders are themselves very techincally-minded, or if the project is of a sufficiently technical level, that the Gherkin tests themselves would be written in a very techsome manner.&lt;/p&gt;
&lt;p&gt;Suppose I have a project of standing up a new microservice to aggregate various events into &lt;code&gt;doodads&lt;/code&gt; for other internal services. The few stakeholders that there are know what a &lt;code&gt;doodad&lt;/code&gt; is, what a microservice is, and so on. I should have no problem with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When a &amp;quot;create-doodad&amp;quot; even is published with the body
    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;DoodadId&amp;quot;: 12,
        &amp;quot;Name&amp;quot;: &amp;quot;Bob&amp;quot;
    }
    &amp;quot;&amp;quot;&amp;quot;
And I send a GET request to &amp;quot;doodad-api/v1/doodads/12&amp;quot;
Then the response should have status code 200 with body
    &amp;quot;&amp;quot;&amp;quot;
    {
        &amp;quot;doodadId&amp;quot;: 12,
        &amp;quot;name&amp;quot;: &amp;quot;Bob&amp;quot;
    }
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everyone understands concepts like events, event and response bodies, GET requests, and the whole shebang, then I suggest there's no issue with the above. In fact, being able to reduce the number of Gherkin steps to a small number of more generic, more technical steps can greatly simplify the testing. On a smaller project that's advantageous!&lt;/p&gt;
&lt;p&gt;Now, this example is so technical as to bring into question why Gherkin is used at all - the tests read almost just as the code does. Truly, if there's no need for the Gherkin abstraction, then by all means don't use Gherkin. I did recently find myself in this position where the non-engineering stakeholders (such as a project manager, business analyst, and the like) knew perfectly well what HTTP requests and the like were - and preferred to define business rules in those terms - but would have found themselves too slowed reading actual test code. The uber-techy Gherkin provided exactly the right level of abstraction to facilitate collaboration and a successful BDD process.&lt;/p&gt;
&lt;p&gt;So, don't be too dogmatic! There'll be plenty of times that various degrees of tech-speak need to make it into your Gherkin, and when done with respect to the expectations and capabilities of the project's stakeholders it can be a distinct benefit.&lt;/p&gt;
</description>
      <pubDate>Tue, 31 Dec 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-12-31T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">ive_indiewebbed_my_site</guid>
      <link>https://ian.wold.guru/Posts/ive_indiewebbed_my_site.html</link>
      <title>I've IndieWebbed My Site</title>
      <description>&lt;p&gt;The &lt;a href="https://indieweb.org/"&gt;IndieWeb&lt;/a&gt; is a small and loose collection of formats and protocols which allow those of us with our own sites to identify each other and their content across the web. Even that description is a bit much - there's only the &lt;a href="https://webmention.net/"&gt;webmention&lt;/a&gt; protocol and a couple so-called &lt;a href="https://indieweb.org/microformats"&gt;microformats&lt;/a&gt; for identifying people and content.&lt;/p&gt;
&lt;p&gt;Obviously I own my own site, and I had to do &lt;em&gt;very&lt;/em&gt; little work to ensure that the markup of this site conformed to the flexible specifications of the microformats. With this all in place, I can &lt;a href="https://indieweb.org/IndieAuth"&gt;authenticate&lt;/a&gt; myself using my site, others can &lt;a href="https://indieweb.org/h-card"&gt;identify me by my domain&lt;/a&gt;, y'all can &lt;a href="https://indieweb.org/h-entry"&gt;scrape my site for posts&lt;/a&gt;, and more. This gives a common foundation for individuals to be able to control their own content and exposure to the wider web. By using the &lt;code&gt;h-card&lt;/code&gt; microformat to &lt;em&gt;link&lt;/em&gt; to each other across blogs, it also decentralizes the relationships between this content, obivating the utility of scoail media platforms as providers of these relationships.&lt;/p&gt;
&lt;p&gt;The glue that holds it together is the webmention protocol. This is a simple way for someone who publishes content to notify another when an author mentions someone on their site, the idea being that each person maitains a listener at a certain endpoint (identified by a &lt;code&gt;&amp;lt;link rel=&amp;quot;webmention&amp;quot;&amp;gt;&lt;/code&gt; header tag) that will listen for and save pings from others, and then display them on their site. My webmention URL is &lt;code&gt;https://webmention.io/ian.wold.guru/webmention&lt;/code&gt;, and if you webmention one of my posts it wil lshow up at the bottom of the post! The other half is publishing of course - I'm working on a clean way for this site's &lt;a href="https://github.com/IanWold/ianwold.github.io/blob/master/.github/workflows/build.yml"&gt;build pipeline&lt;/a&gt; to send these pings. The tricky part is a ping should only be sent once, when the mention is first published. I used the excellent site &lt;a href="https://webmention.io"&gt;webmention.io&lt;/a&gt; In order to set up receiving and querying webmentions. A &lt;a href="https://github.com/IanWold/ianwold.github.io/commit/8e16adb457ee367c159635f66710c82363e8b4a9"&gt;simple script&lt;/a&gt; queries webmentions for each article and displays them.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://indieweb.org/"&gt;IndieWeb wiki&lt;/a&gt; was an excellent resource that got me quite excited about the potential for this idea. The intent seems to be that this is a supplement to social media, but I see potential in it as an alternative to social media. I'm not a fan of any of the social platforms nowadays, and attempts at federated platforms like Mastodon don't seem to provide all that much for me. The notion that I might be able to replace the conversation aspect of social media with something as simple as the IndieWeb is attractive.&lt;/p&gt;
&lt;p&gt;I'd encourage everyone with their own site to implement at least the IndieWeb formats. &lt;a href="https://indiewebify.me/"&gt;IndieWebify.me&lt;/a&gt; is great for testing your site, and there's a lot more resources in the wiki. If you're not so keen on coding your own site and want to use a hosting provider instead, it happens that WordPress will have no problem IndieWebifying you, and there's &lt;a href="https://indieweb.org/web_hosting"&gt;several other hosts&lt;/a&gt; you can consider as well. Finally, there are &lt;a href="https://indieweb.org/discuss"&gt;chats set up in Slack, IRC, and Discord&lt;/a&gt; to help get going.&lt;/p&gt;
&lt;p&gt;Is there some IndieWeb component which I'm missing on this site but I should include? Webmention me and I'll set it up; this is an ongoing project for me!&lt;/p&gt;
</description>
      <pubDate>Mon, 01 Apr 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-04-01T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">ive_stopped_using_visual_studio</guid>
      <link>https://ian.wold.guru/Posts/ive_stopped_using_visual_studio.html</link>
      <title>I've Stopped Using Visual Studio</title>
      <description>&lt;p&gt;One year ago, Microsoft released the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit"&gt;C# Dev Kit&lt;/a&gt; extension for VS Code, promising to bring the solution explorer to the editor along with quality of life updates around building, packages, testing, and other niceties. I was doing a fair amount of JavaScript at work at the time this was released, so I thought I'd install it in VS Code and use that editor for all my work instead of switching between editors several times a day.&lt;/p&gt;
&lt;p&gt;Over the last year, I've opened Visual Studio less and less. There are some legacy .NET Framework projects I need to use it for, otherwise all of my work is done in VS Code. This is due to a few factors: I can actually develop in several langauges, I can customize it better, its extensions allow me to use it for more workflows than just development, and I find I'm actually more productive in the simpler environment. To be clear, I'm not and never have been a Visual Studio hater - I don't like Rider or ReSharper, I've always been very happy with Visual Studio. I just think the simpler editor leads to a better workflow.&lt;/p&gt;
&lt;h1&gt;Customizations&lt;/h1&gt;
&lt;p&gt;To start with, I mapped all of the panels on the activity bar to key commands. &lt;code&gt;CTRL+SHIFT+_&lt;/code&gt; gets me around all the panels, and I only mapped keys on the left of the keyboard for quick, one-hand navigation. &lt;code&gt;x&lt;/code&gt; opens the _e_xplorer, &lt;code&gt;c&lt;/code&gt; opens version _c_ontrol, &lt;code&gt;space&lt;/code&gt; opens or hides the bottom panel, &lt;code&gt;a&lt;/code&gt; closes the side panel entirely, and so on. I can keep the activity bar closed and get around everywhere I need quickly.&lt;/p&gt;
&lt;p&gt;Then I made some UI updates so that the tab bars at the top of the editor are much narrower, and I cleared up the clutter on the status bar. I installed &lt;a href="https://marketplace.visualstudio.com/items?itemName=jtlowe.vscode-icon-theme"&gt;Studio Icons&lt;/a&gt; because after the last 14+ years of using Visual Studio I'm still used to its icons. Finally, I imported by &lt;a href="https://packagecontrol.io/packages/Monokai%20Gray"&gt;Monokai Gray&lt;/a&gt; theme, which I originally made for Sublime but now use on all my editors.&lt;/p&gt;
&lt;h1&gt;Background&lt;/h1&gt;
&lt;p&gt;The best idea I had though was to install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Katsute.code-background"&gt;Background&lt;/a&gt; extension. This extension allows you to specify background images for your code panels. I know it sounds like a distraction or a pain when you first consider the idea, but in practice it's anything but.&lt;/p&gt;
&lt;p&gt;I installed it and loaded it with several different-colored space wallpaper images (one is red, one is blue, one is green, and so on). This has made a huge improvement in my ability to keep groups of tabs distinct when working. I use an ultrawide monitor most of the time, so it's not rare that I have six tab groups all open side-by-side. Now they're all (gently) colored! Not to mention, i makes me look like one of the cool kids 😎&lt;/p&gt;
&lt;h1&gt;Other Extensions&lt;/h1&gt;
&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl"&gt;WSL&lt;/a&gt; extension has made working in the WSL so much simpler. It wasn't necessarily difficult before, but if you d oany work in the WSL this one will sell VS Code to you (if you're not using it already - it's been well-known for some time that VS Code works well with it).&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode"&gt;Postman&lt;/a&gt; extension is so good that I barely need to open the Postman application anymore! It's really excellent to be able to keep Postman open in the same context I'm using to edit code. I don't use Postman to execute local requests for debugging, but when you need to test calls to services you're connecting to in your code it keeps the workflow entirely inside VS Code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github"&gt;GitHub Pull Requests&lt;/a&gt; and other extensions from GitHub have allowed me to do the majority of my GitHub work right from th editor. Anyone who's reviewed PRs from their editor will (I'm assuming) be quick to describe how much better the experience is to see code changes in the context of the whole codebase instead of through the small window on the online interface.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;a href="https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor"&gt;Excalidraw&lt;/a&gt; extension is really excellent. Im' a big fan of Excalidraw not just as a product, but I'm impressed by the development they've put into the product. With this exension I keep all my diagrams as local &lt;code&gt;.excalidraw&lt;/code&gt; files, and I edit them all from the editor.&lt;/p&gt;
&lt;p&gt;The best part about the last three extensions is they all use my space backgrounds too!&lt;/p&gt;
&lt;h1&gt;Workflow&lt;/h1&gt;
&lt;p&gt;Using VS Code for the last year has had an improvement, I think, on my workflow. For the most part, the editor stays out of the way. At first configuring building and testing took a bit of doing, but not more than a couple days' worth of reading docs online. I'm friends with the editor now, and its simplicity (or maybe its &lt;em&gt;ability&lt;/em&gt; to be simple) is its strongest suit.&lt;/p&gt;
&lt;p&gt;That does mean, of course, that there are a lot of features it doesn't support through the UI. Of course, anything can be done through the terminal, and the editor makes that very easy as well. I have &lt;code&gt;CTRL+SHIFT+SPACE&lt;/code&gt; mapped to open the panel, and it opens right to the terminal now. I make a lot of use of that anymore; I barely did when using .NET for C# projects. For complicated operations - particularly some of the more finicky Git things - the terminal is a much better tool anyway.&lt;/p&gt;
&lt;p&gt;Almost every small problem I have with the editor either has a workaround or different flow to avoid it, or gets fixed in the not-too-distant future. The one major gipe I have left though is that it &lt;em&gt;is&lt;/em&gt; an Electron app. Microsoft has gone a fair way to address some of the performance issues resulting from that, but they are still there. F12 (go to definition) takes noticeably longer in VS Code than Visual Studio. Even button clicks seem to take an extra split second to register. Even opening a file is faster in the other.&lt;/p&gt;
&lt;p&gt;At the end of the day though, I'm incredibly happy with the editor and the workflow I've found with it here. I'm going to be keeping it going forward, and I'd recommend anyone on Visual Studio give it a try.&lt;/p&gt;
</description>
      <pubDate>Mon, 30 Sep 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-09-30T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">ive_switched_to_linux</guid>
      <link>https://ian.wold.guru/Posts/ive_switched_to_linux.html</link>
      <title>I've Switched to Linux</title>
      <description>&lt;p&gt;I guess this is more of a short announcement post, but I would encourage other .NET engineers to consider doing the same. As .NET has progressed, its support on Linux has gotten better and better. The VS Code C# Dev Kit has all of the features from VS that I ever need to develop, and I don't find myself needing to develop a whole lot of super-Windows-specific desktop applications anymore.&lt;/p&gt;
&lt;p&gt;With the .NET 10 release and a round of tooling updates, I've found that all of my development requirements are completely supported on Linux. Windows is increasingly bloated (and increasingly without the ability to unbloat it), the update experiences are extremely frustrating, and the sharp increase - even for Microsoft - in privacy violations as of late makes Windows more of a burden than a help, even though I primarily use Microsoft technologies in my day-to-day development.&lt;/p&gt;
&lt;p&gt;The reality is that I can do all of the .NET development I want to from a much more lightweight system, &lt;em&gt;and&lt;/em&gt; I can close the book on worrying about (an amount of) Microsoft's spying. This is a step I've been hoping to take for a long time, and just as well it comes at this time of year that I can call it a Christmas present to myself!&lt;/p&gt;
</description>
      <pubDate>Thu, 18 Dec 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-12-18T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">i_have_a_blogroll_now</guid>
      <link>https://ian.wold.guru/Posts/i_have_a_blogroll_now.html</link>
      <title>I Have a Blogroll Now!</title>
      <description>&lt;p&gt;A bit ago I collected &lt;a href="https://ian.wold.guru/Posts/book_club_9-2024.html"&gt;a list of links&lt;/a&gt; to blogs that I enjoy tuning into. I think this is a good list of blogs; these are the ones I enjoy coming back to with some regularity - even the ones which haven't posted in a while have great articles that are worth revisiting.&lt;/p&gt;
&lt;p&gt;There's a couple problems here though. First, I'm generally averse to blog posts with lots of updates; I think a post is a relatively static thing, so updates should be limited to correcting spelling/grammar errors and the occasional clarification. Thus, as I collect more blogs I'm disinclined to keep &lt;em&gt;that&lt;/em&gt; page updated. The second problem is reading them: they're links in my browser and it's quite sporadic how I go about reading them. I've got most of them saved in an RSS reader on my phone but I tend to go months without remembering that that exists.&lt;/p&gt;
&lt;p&gt;So I want a couple things: I want a dynamic page that I can update with new RSS feeds I want to tune into, and I want an easy way to be able to catch updates from these blogs. The obvious step is to add a page at the top of this site that contains the list; this isn't a post so I can update it as frequently as I want (like my &lt;a href="https://ian.wold.guru/now.html"&gt;now page&lt;/a&gt;), but then this still requires that I go onto this page and click through to read the blogs. That's not much better than having a favorites bar, though it does make the blogroll public.&lt;/p&gt;
&lt;p&gt;It's obviously advantageous that these are RSS feeds; I should be able to set up some sort of scheme to get the latest posts from them. To me, the best place for this would be my browser homepage; I like to start my day a bit lighter before an onslaught of morning meetings, so if I could have a reading list right when I get started I'll be more inclined to tune in more frequently.&lt;/p&gt;
&lt;p&gt;As it happens, I do have a project that I haven't finished - a &lt;a href="https://github.com/IanWold/Metalsharp"&gt;static site generator&lt;/a&gt; which I use to generate this blog. I already know how to hook that up with GitHub Actions and Pages to get a static site really easy, so it really shouldn't be any work to set up a site that can host my bogroll and pull the latest posts from their feeds!&lt;/p&gt;
&lt;p&gt;Indeed, it wasn't difficult to set up a first pass. I just have a JSON with a list of RSS links, and a C# script to download these RSSes, parse them, and build the site. It's just two pages: a list of blogs and a list of their latest articles (the latter page now being my browser homepage). What a deal! I didn't want to get bogged down in UI design or any super-fancy coding, so I stuck to the C# script setup that I know works, and I used &lt;a href="https://andybrewer.github.io/mvp/"&gt;MVP.css&lt;/a&gt; to give an attractive style to plain, old HTML. Super simple and out the door in no time!&lt;/p&gt;
&lt;p&gt;This did replace &lt;a href="https://ian.wold.guru/Posts/daily_grug.html"&gt;Daily Grug&lt;/a&gt; as my homepage, so I'm going to need to get the daily grug quote inserted into the page.&lt;/p&gt;
&lt;p&gt;I'll encourage you all to check out &lt;a href="https://ian.wold.guru/Blogroll/"&gt;my blogroll&lt;/a&gt;! If you've got suggestions for blogs I should follow (or your own blog), comment below or &lt;a href="https://ian.wold.guru/connect.html"&gt;reach out to me&lt;/a&gt; on any of my other channels, I'd love to find more.&lt;/p&gt;
&lt;p&gt;In future I'll be putting in a bit of work to set up a template repo in GitHub to make it easy for anyone to use this scheme to publish a blogroll - stay tuned!&lt;/p&gt;
</description>
      <pubDate>Fri, 01 Nov 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-11-01T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">i_like_petite_vue</guid>
      <link>https://ian.wold.guru/Posts/i_like_petite_vue.html</link>
      <title>I Like Petite-Vue</title>
      <description>&lt;p&gt;I like simple things in software: simple engineering, simple architecture, simple tools. The primary, almost entire, focus of our jobs in software is to wrangle the &lt;a href="https://grugbrain.dev/"&gt;demon spirit complexity&lt;/a&gt;, the best tool for which is a focus on necessity: what is &lt;em&gt;necessary&lt;/em&gt; to build a thing, and what is &lt;em&gt;necessary&lt;/em&gt; about that thing?&lt;/p&gt;
&lt;p&gt;If you need a personal site (hello!) then a statically generated page is much simpler than writing your own server. Server not necessary! If you are generating a static site, you might find that &lt;a href="https://ian.wold.guru/Posts/90_of_my_homepage_was_useless.html"&gt;90% of it isn't necessary&lt;/a&gt; - and that's bytes, not content! If you need a page with interactivity, React is probably overkill for you; &lt;a href="https://alpinejs.dev/"&gt;Alpine&lt;/a&gt; might be all you really need.&lt;/p&gt;
&lt;p&gt;I like Alpine a lot! In order to get all of the interactivity you'll ever really need for most simple applications, it's got the right level of learning at a low package size. It's easy to recommend for a lot of smaller applications, but there's some limitations. &lt;em&gt;Very&lt;/em&gt; small pages make use of query selectors just fine, and applications on even the &lt;em&gt;slightly&lt;/em&gt; larger side might, depending on the domain, be a bit wary of adopting a tool which might not be able to satisfy changing requirements long-term. A lot of applications don't have vastly changing requirements over their lives. Some do. Products almost certainly do.&lt;/p&gt;
&lt;p&gt;These are two problems that are solved by &lt;a href="https://github.com/vuejs/petite-vue"&gt;Petite Vue&lt;/a&gt; (in addition to there being, or maybe having been, a &lt;a href="https://markaicode.com/alpine-js-vs-petite-vue-performance-comparison/"&gt;&amp;quot;minimal&amp;quot; memory leak in Alpine&lt;/a&gt;). As its name suggests, the project is a subset of Vue that provides a similar functionality as Alpine. After resolving dependencies it has on the main Vue project it still comes in well under the size that Alpine does and it runs faster! It lacks support for extensions like Alpine has, but being a subset of Vue, you can swap Petite Vue out for regular Vue and none of your code will need to change. This makes it a lot easier to recommend for very small projects as well as those which need to accommodate changing requirements. It satisfies more needs.&lt;/p&gt;
&lt;p&gt;The neatest part is that it's complete; you'll see no commits in three years (as of this writing) on their repo! So much software can't ever be completed, too frequently the product is not small or concise enough. We often look to the commit frequency on a project to be a determiner of status, that a product in &amp;quot;active&amp;quot; development is more worth investing in, but this seems entirely backwards to me. If they're always fixing bugs, isn't this an indicator that the project is unstable? If they're always introducing new required features, isn't this an indicator that the project is underbaked? If they're always introducing new unnecessary features, isn't this an indicator that the project is bloating?&lt;/p&gt;
&lt;p&gt;So I like Petite Vue. It's a role model, I think, for the best kind of software development: it exemplifies the focus on necessity.&lt;/p&gt;
</description>
      <pubDate>Fri, 27 Jun 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-06-27T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">just_use_postgresql</guid>
      <link>https://ian.wold.guru/Posts/just_use_postgresql.html</link>
      <title>Just Use PostgreSQL</title>
      <description>&lt;p&gt;There are a lot - and I mean &lt;em&gt;a lot&lt;/em&gt; - of options when you're considering a database to use. Every application has different data, making new, specialized databases interesting to us as potential options; it's often desirable to work with tools that support our specific use cases. More often than not though (heck, the vast majority of the time) storing data is just that - &lt;em&gt;storing data&lt;/em&gt;. Specialization is good but if it's not really needed it's easy to become too constrained or too complex. Not to mention that as requirements change our systems often evolve beyond the constraints we start with, making it a bit tougher to choose a very specialized database.&lt;/p&gt;
&lt;p&gt;I think that for the vast majority of systems, PostgreSQL is the best choice. It is open-source, performant, secure, and supports any data model or pattern you need. It's well-documented for all of its use cases, the tooling ecosystem around it is excellently mature; heck, darn near everything about it is excellently mature. I think it's the easiest database to get started with for just about any use case, and it's able to extend with your requirements more than any other database.&lt;/p&gt;
&lt;p&gt;This is entering opinion territory, but I think that unless you have truly one-of-a-kind requirements, chosing PostgreSQL is the best way to set your new project up for success.&lt;/p&gt;
&lt;h1&gt;PostgreSQL Supports Your Data Model&lt;/h1&gt;
&lt;p&gt;As requirements change, your data model can change for part or all of your application. I don't like being constrained by my database to a single data model or a set of models, and I don't like being shut out from implementing any patterns I need. Sometimes different data needs to be represented differently, or I might need to use different patterns for different domain contexts in my data, while still needing to maintain references between them. Only a system as widely capable and documented as PostgreSQL can allow me to do that.&lt;/p&gt;
&lt;p&gt;Obviously PostgreSQL is a relational database, but it can support any data model you need. Built-in support for JSON data gives you everything you need for a document store. You can code your own solutions or use widely popular extensions for graph or column-family models. You can get quite far with native PostgreSQL, and well-maintained, well-documented, and well-used extensions can get you the rest of the way if you have more complicated requirements.&lt;/p&gt;
&lt;p&gt;Any patterns you can think of are supported as well. Event sourcing, which can particularly complicated, has a lot of documentation. A lot of patterns like versioned schemas or CQRS aren't related to the specific database you choose, but PostgreSQL and its excellent community documentation makes using these patterns effortless.&lt;/p&gt;
&lt;h1&gt;Your PostgreSQL Database can Perform and Scale&lt;/h1&gt;
&lt;p&gt;Especially with reent versions, PostgreSQL offers excellent support for performance tuning, and tools like Citus can make scaling to any size quite easy. That's relatively speaking of course; optimizing any database is necessarily complicated, and distribution is never an easy problem. But PostgreSQL has the tooling, documentation, and maturity to ensure you can get there with your use case.&lt;/p&gt;
&lt;p&gt;Being largely open-source, there's a good chance you can set your PostgreSQL environment up without spending anything on the database system itself - ideally all of your costs can be on your infrastructure providers. Being as popular and long-lived as it is, PostgreSQL can run on just about any infrastructure you might be interested in provisioning for it.&lt;/p&gt;
&lt;h1&gt;Don't Use a Document Database&lt;/h1&gt;
&lt;p&gt;Document databases are excellent for non-relational data. And being fair, there's a huge amount of non-relational data out there, particularly across the maybe millions of microservices we've created. If you ever end up needing to persist relational data though - and this isn't an unlikely change in your future requirements - you'll be in a bit of a pickle. Yes, most document databases can support relational data to a greater or lesser extent, but if you've got relational data you want a &lt;em&gt;relational database&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I think for the vast majority of use cases, PostgreSQL is as capable a document database as any other popular choice. There's even libraries for most languages that allow you to use PostgreSQL with an interface more akin to popular document database drivers. But then when you need to start storing relational data, you can start using PostgreSQL in that capacity without needing to migrate your data to a different system, and without using a database that doesn't naturally support it.&lt;/p&gt;
&lt;h1&gt;When to Not Use PostgreSQL&lt;/h1&gt;
&lt;p&gt;The other databases exist for reasons though, and I don't want to be taken as advocating an exclusively PostgreSQL approach. There are plenty of edge scenarios where you'll want a different database, but I caution that these scenarios are &lt;em&gt;very much the exception&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you need to serve extremely high volumes of high-performant real-time analytics, BigQuery probably makes sense over PostgreSQL. If your application absolutely requires the highest write throughput that humanity's capabilities can muster, then a document database or KV database optimized for writes will outperform PostgreSQL. Similarly, if you're handling truly planet-scale amounts of data (many petabytes) then you should probably be reading other blogs altogether, actually.&lt;/p&gt;
&lt;p&gt;The case you're more likely to encounter (note I did not say you're likely to encounter, just that it's more likely) is when your application - say, a microservice - is uniquely, wholy (not partly), and inextricably reliant upon the specific data structures, patterns, or capabilities offered by an alternate, specialized database. I caution you to re-read the qualifiers I put on that statement: uniquely, wholy, and inextricably. If your data is mixed model or paradigm, it probably makes sense to have it all in a PostgreSQL database. If you need referential integrity between the data in the different models, then it definitely makes sense to keep it in PostgreSQL. But Elasticsearch is spectacular if you've got a microservice exclusively for complex searches, and EventStoreDb is great if you've got a state machine microservice (do those exist?).&lt;/p&gt;
&lt;h1&gt;But Really, Just Use PostgreSQL&lt;/h1&gt;
&lt;p&gt;Those edge cases truly are at the edge of what we need to support. The overwhelming set of persitence needs for our systems is well-handled by PostgreSQL. The majority of scenarios that are supported are extremely well supported, and PostgreSQL will be very competent for everything else.&lt;/p&gt;
</description>
      <pubDate>Sat, 13 Jan 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-01-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">lateny_is_zero</guid>
      <link>https://ian.wold.guru/Posts/lateny_is_zero.html</link>
      <title>Latency is Zero and the Speed of Light is Getting Faster</title>
      <description>&lt;p&gt;Did you know that the speed of light is getting faster? It is! 10 years ago it was 10 mm per clock cycle. 5 years ago it was 5 mm per clock cycle. Today it's 3 mm per clock cycle! This is good news to those of us attempting to solve latency issues in distributed systems because the speed of light is a natural barrier that cannot be overcome; this imposes a limit on all travel, including &lt;em&gt;information travel&lt;/em&gt;, so the universe dictates a natural level of latency in all of our applications. A distributed system at scale will be particularly affected by this. &amp;quot;Latency is Zero&amp;quot; is the second Fallacy of Distributed Computing because of this - we can't ignore latency or assume it away because it is &lt;em&gt;always&lt;/em&gt; a baseline effect on &lt;em&gt;every&lt;/em&gt; operation.&lt;/p&gt;
&lt;p&gt;David Boike, writing for Particular, &lt;a href="https://particular.net/blog/latency-is-zero"&gt;gives a succinct definition of Latency&lt;/a&gt; in terms of transmission time, size, and bandwidth: &lt;code&gt;TotalTime = Latency + (Size / Bandwidth)&lt;/code&gt;. Latency is an additional cost incurred by &lt;em&gt;every&lt;/em&gt; operation. Even local, CPU-bound operations still need to travel over a short wire within the client machine to resolve. This is low latency. A request from a client to a server is going to incur a &lt;em&gt;significantly&lt;/em&gt; higher amount of latency - this must be accounted for. In a distributed system, every single inter-service operation incurs this latency. That makes this a big problem at scale.&lt;/p&gt;
&lt;h1&gt;Resolving Latency We Control&lt;/h1&gt;
&lt;p&gt;Unlike &lt;a href="https://ian.wold.guru/Posts/the_network_is_reliable.html"&gt;network reliability issues&lt;/a&gt;, the latency issue actually can be mostly overcome. Well, in theory it can; in practice probably not. And it depends on what kind of latency you care about. And only within the confines of your server components. Do you have total control over all of the latency in your system, and have you designed your system in such a way that no component is temporally dependent on another? I'm not sure it's possible for any real-world system to match that, but no doubt this is an area where theory can inform practice.&lt;/p&gt;
&lt;h2&gt;Maximize Utility-per-Call&lt;/h2&gt;
&lt;p&gt;Latency occurs on each operation we perform, but it's most egregious - and most impactful to us - on network calls. If we have more network calls we have more latency, and less latency with fewer calls. This doesn't eliminate latency itself from being a problem, but it lessens the effects.&lt;/p&gt;
&lt;p&gt;Consider a microservices system where a gateway API might call into an orders service, which itself needs to call into an items service to get item information to return with an order, which itself might need to call into an availability service to return that data with the item. Assuming a baseline 50ms latency per network call (sometimes this is a lot more) our total latency in a single call is 200ms from the client. If we could shave two calls off that reduces our total latency to 100ms. Keep in mind though latency is usually more than 50ms!&lt;/p&gt;
&lt;p&gt;Eliminating calls works to a point, obviously we still need to make network calls at some point. When we do make network calls, we want to make sure we &lt;em&gt;need&lt;/em&gt; to make that call, and we need to make sure they're returning exactly the data we need. On top of this, sometimes calls can be combined - if we observe a pattern where service A regularly makes two calls to service B, that's an indication that service B should make a new endpoint available that condenses the data from both calls. This halves the latency incurred between the two services.&lt;/p&gt;
&lt;p&gt;Remember that maximizing call utility does not mean &amp;quot;cram as much crap as possible in each request&amp;quot;. Bandwidth still matters, and each temporally-coupling call must be absolutely necessary.&lt;/p&gt;
&lt;p&gt;An important pattern to consider in maximizing the utility of each request is database connection pooling. Opening a new database connection can be costly, so this pattern suggests that we should maintain a &amp;quot;pool&amp;quot; of open database connections that can be reused for different requests. For applications which frequently interact with their database, this technique can significantly reduce latency in this specific scenario. This is especially important since &lt;em&gt;most&lt;/em&gt; services will maintain some form of persistence, so database interactions are a key target to improve metrics in this respect.&lt;/p&gt;
&lt;h2&gt;Asynchronous Communication and Eventual Consistency&lt;/h2&gt;
&lt;p&gt;As I've covered before &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;in my series on the Fallacies&lt;/a&gt;, asynchronous communication can help resolve a lot of network issues. To review, &amp;quot;synchronous&amp;quot; communication is when we need to communicate between distributed components in real-time to satisfy a request. When a system need to receive a response to a request before it can proceed with its task, it is said to have a temporal coupling here.&lt;/p&gt;
&lt;p&gt;With respect to latency, these temporal couplings are a primary concern. Moving to a pattern of asynchronous communication - where communication between two distributed components is &lt;em&gt;not&lt;/em&gt; required to satisfy a request - removes latency as a concern with respect to satisfying these requests. Asynchronous communication is achieved by having the components of the system publish and subscribe to events on state change. Instead of requiring Service A to request a resource from Service B when it needs it, we would rather have Service B publish an event whenever its resources are modified and to have Service A listen to these events and update its own persistence with the information it requires from that resource.&lt;/p&gt;
&lt;p&gt;To be clear, this shifts where the latency is in our system. When I make a request from Service A, I will &lt;em&gt;not&lt;/em&gt; experience the latency that would otherwise occur if that call was temporally coupled to Service B. However, there is an observable period of time between when Service B's resource is updated and when Service A is able to reflect that update. This resource is said to be eventually consistent between the two services; there are periods of time where they have different understandings of the resource. Sometimes this is OK; other times not.&lt;/p&gt;
&lt;p&gt;If you have a system which is 100% eventually consistent and communicating entirely asynchronously, then you've eliminated request latency as a concern. But I'm betting your client still needs to contact its server, and I'm betting you'll need a temporally-coupled call here or there.&lt;/p&gt;
&lt;p&gt;To be clear also, this strategy introduces its own concerns. You will end up with inconsistent data, for example - how do you resolve this? &lt;em&gt;Can&lt;/em&gt; you?&lt;/p&gt;
&lt;h2&gt;Beware the Cache Demon&lt;/h2&gt;
&lt;p&gt;Caching is a tempting solution for a lot of network issues since it eliminates the need to make network calls under some circumstances. This is the catch though, our caches need to be particularly aware of what those circumstances are. When to cache, what to cache, and when to clear what from the cache are difficult questions, and misapplying a cache can lead to confusing and difficult errors. As the saying goes, there's only two hard things in Computer Science: naming things, cache invalidation, and off-by-one errors. Cache invalidation is difficult enough that I do a double take whenever the word is used in a meeting.&lt;/p&gt;
&lt;p&gt;Understanding cache patterns will help to determine where, if anywhere, to introduce one. A caution though, fewer caches is always better - if you're able to avoid them entirely that might be just as well so as to eliminate a potential headache. Ryan French has an excellent overview of some related cache patterns on &lt;a href="https://ryan-french.medium.com/the-fallacies-of-distributed-computing-latency-is-zero-14a02a73f43a"&gt;his article on this Fallacy&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;External Factors&lt;/h1&gt;
&lt;p&gt;Truly though, we don't have control over all of our latency. Most systems of an appropriate size or utility need to connect to some external services. Cloud providers are a primary culprit here since most of our server computing uses them nowadays. Are you using AWS lambdas or Azure's identity provider? You might own the application logic within the Lambda or the data within the ID provider, but that's wrapped in their own system; you're interfacing with a component that you don't necessarily control.&lt;/p&gt;
&lt;p&gt;So while you can't control all of your latency, you still do get to control your network topology. By carefully designing and managing the configuration and traffic patterns between the system components you can &amp;quot;box in&amp;quot; and reduce reliance on potentially volatile areas. I'd suggest that understanding network topology is more essential for identifying and addressing latency issues within distributed environments.&lt;/p&gt;
&lt;h2&gt;Topology Management&lt;/h2&gt;
&lt;p&gt;I considered naming this section &amp;quot;alphabet soup&amp;quot; - there's lots of patterns with three-letter acronyms in this domain. This can be its own article entirely, and topology will be related to the other Fallacies as well, so I'll just touch on a couple of things to consider for further research.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Software-defined_networking"&gt;Software-defined networking (SDN)&lt;/a&gt; is the commonly-cited approach here; this suggests that software should be employed in defining the network topology and intelligently routing communication across the network. By allowing software to intelligently control network traffic, your system can more intelligently react to various network conditions. This is overkill if you've got a client-server setup, but necessary if you're operating at any scale.&lt;/p&gt;
&lt;p&gt;It's a good idea to couple this with &lt;a href="https://en.wikipedia.org/wiki/Network_function_virtualization"&gt;network function virtualization (NFV)&lt;/a&gt;, which suggests that various vital components of the network can be run on virtualized environments to allow for better management. Resources like firewalls and load balancers that are necessary across the entire system can be scaled horizontally as needed. If all of your components are operating in one or several container clusers, then this is probably already done for you.&lt;/p&gt;
&lt;p&gt;Both of these strategies allow us to use existing systems that can react to latency in the network and aids the system at scale. They don't eliminate latency as a problem, they mitigate the effect of the problem when it happens.&lt;/p&gt;
&lt;h2&gt;Geography&lt;/h2&gt;
&lt;p&gt;The physical distance between server and client is a fundamental factor affecting latency—data packets don't travel instantaneously. This isn't just theory; it's a practical concern that affects the responsiveness of distributed systems on a global scale. A request sent from Minneapolis to St. Paul is naturally going to complete faster than one sent from New York to Hong Kong, mostly because the data has less distance to travel but also because there is much more bandwidth between Minneapolis and St. Paul - the wires between the two cities support more traffic than they need, while the wires between New York and Hong Kong are strained with much more traffic.&lt;/p&gt;
&lt;p&gt;We might be attracted to &lt;a href="https://en.wikipedia.org/wiki/Edge_computing"&gt;edge computing&lt;/a&gt; to alleviate this, and intelligently-applied this can be quite good. We do want to keep resources geographically close to their consumers. This is quite the double-edged sword though. Moving too many processes to the edge can decentralize system management to a problematic degree, leading to difficulties in maintaining consistency, security, and operational oversight. There's a balance to be struck between leveraging the edge to improve latency and ensuring that the system remains manageable and secure. &lt;a href="https://www.youtube.com/watch?v=ze3uhkC4534"&gt;Theo-T3 has an interesting perspective&lt;/a&gt; moving away from edge computing recently.&lt;/p&gt;
&lt;h2&gt;External Systems&lt;/h2&gt;
&lt;p&gt;In my mind, the most concerning latency issues is that we (probably) can't control all of our own sources of latency. As I mentioned, you're almost certainly communicating with an external system, and if you're not then you're probably on a cloud provider that's giving you some form of abstraction.&lt;/p&gt;
&lt;p&gt;Suppose one of these systems changes their network topology, or some other information. Suppose &lt;em&gt;that&lt;/em&gt; system moves its Kubernetes cluster from a fast cloud provider to a slow one. You've suddenly got more latency in your request.&lt;/p&gt;
&lt;p&gt;To mitigate this, it might be necessary to adopt more aggressive strategies towards these external services. Implementing timeout policies, circuit breakers, or message queues (&lt;em&gt;ahem&lt;/em&gt; did somebody say &amp;quot;Enterprise Service Bus&amp;quot;?) can safeguard your system against the unpredictability of external dependencies. These measures give you some control back, but there's no approach that keeps your system perfectly safe.&lt;/p&gt;
&lt;h1&gt;Catching Latency When it Happens&lt;/h1&gt;
&lt;p&gt;Because we can have latency thrust upon our poor systems, part of our job is to be able to react to it when it happens. We can proactively guard against latency enough to be reasonably sure that we probably aren't going to hurt ourselves, but we need a reactive posture against that latency we don't control. This means testing and monitoring, of course.&lt;/p&gt;
&lt;p&gt;While the specific, technical considerations in setting up monitoring are out of the scope of this article, it's really not difficult nowadays to set up a basic, sufficient level of latency monitoring - there are tools which can watch and log the times of network calls, and these systems can usually be configured to ping you (or, ideally, someone else who gets paid more than you) if latency spikes at 2 AM. You might not need to react immediately in these cases, but it's vital here for the team owning a distributed system to set up a monitoring feedback loop, and maybe to include performance targets (latency being key here) as one of their performance indicators.&lt;/p&gt;
&lt;h2&gt;Here we Load Test&lt;/h2&gt;
&lt;p&gt;Because of the relationship between bandwidth and latency (they're defined in terms of each other) issues with the latter are going to arise in environments with issues with the former. That means that latency issues become more apparent as bandwidth issues do, and that means we need to understand the system under load.&lt;/p&gt;
&lt;p&gt;Indeed, load testing systems can capture problems in all the areas we've discussed, giving important insights. We're bound to have bottlenecks &lt;em&gt;somewhere&lt;/em&gt; in our systems, so the task is to identify where those bottlenecks are and whether they're beyond the acceptable parameters of the system. Use incremental load testing to ramp up and identify these areas with more precision. Having a solid grasp of the topology of your network will allow you to design precise load tests that can simulate load across particular geographical or logical areas.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;As with each of the Fallacies - heck, the whole point - is that they need to be kept in mind designing distributed systems. As far as we're concerned about latency, understand what latency you can control and what you can't. Understand your network topology and use monitoring to your advantage to be reactive where you need to be.&lt;/p&gt;
&lt;p&gt;Alas, maybe this is all for naught. After all, the speed of light is getting faster!&lt;/p&gt;
</description>
      <pubDate>Fri, 29 Mar 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-03-29T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">learn_the_old_languages</guid>
      <link>https://ian.wold.guru/Posts/learn_the_old_languages.html</link>
      <title>Learn the Old Languages</title>
      <description>&lt;p&gt;There's a lot of interest in programming languages anymore. I think there always was, but it's become easier over the years for one to make their own language, and it seems like they have. Some of these languages address real-world (if often niche) problems, others are more frivolous. Most probably don't have a great reason for existing other than as a fun project for those involved. Which ones are worth learning? Well, probably not a lot of them. Most of them are interesting projects for small groups of people, though impractical outside an academic interest. A lot of them feel very samey.&lt;/p&gt;
&lt;p&gt;Some of them, to be sure, are worth learning. However, even the new languages in this category don't have a lot of staying power for me. Rust is probably the most useful new language to learn, not just because there's a fair amount done with it now but because it does require that you express your ideas in a different way. Rust is getting well-mature and has a fair community, but it might be one of only two or three from this crop of languages that will stick.&lt;/p&gt;
&lt;p&gt;I think that learning and using a variety of languages is an important practice. Knowledge for its own sake is good, but in this case it offers the very tangible benefit that it improves your thinking and enlightens new ways to consider problems. Knowing multiple (useful) languages makes you more valuable to more companies too - given two otherwise similar candidates, if I'm hiring for a firm which has legacy code in a more esoteric language I'm inclined to hire the candidate with a longer list of languages under their belt, even if the particular language in question isn't listed - it shows an increased engagement with &lt;em&gt;programming&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So if you haven't yet, I would encourage looking back at some of the older languages. Sure, you might have opened up the Wikipedia article for Smalltalk and gawked at its syntax a couple of times, but have you built any OO-suited systems with it? Maybe you had to submit a few Discrete Structures assignments in Prolog in college, but did you ever try to implement real-world algorithms with it? These older, more forgotten languages offer the same benefit in expanding your programming skills while having the distinct advantage of practicality. To be clear, Delphi is nowhere near as practical as C#, but there's sure still legacy code written in it, and as it happens those languages were created by the same guy - just maybe there's some insights to be gained there.&lt;/p&gt;
&lt;p&gt;Here's a small set of languages I'd encourage you learn. Not just to gloss over the syntax, but to sit down and build with. Personal side projects of course - though there are some proper professional uses for these yet, it's probably most advisable to the majority of folks to leave these for fun. The skills you gain by going deep with these languages can translate in real, practical, and sometimes surprising ways into the languages we typically use professionally.&lt;/p&gt;
&lt;h1&gt;Smalltalk&lt;/h1&gt;
&lt;p&gt;Smalltalk is a neat language, though if you've spoken with anyone who has actually worked in Smalltalk before you might get the sense that these folks are just about inclined to split the years on the calendar into &amp;quot;Before Smalltalk&amp;quot; and &amp;quot;After Smalltalk&amp;quot;. So many of them love this language. This is decidedly a language firmly situated in its own time, so you might not find you're able to gain quite that affinity for it today, but it nonetheless has a surprising ability to win over its programmers.&lt;/p&gt;
&lt;p&gt;Smalltalk was, as you might well know, the first object-oriented language. &lt;em&gt;Everything&lt;/em&gt; in the language is an object, and you pass messages instead of making function calls. As Smalltalk initiated the paradigm, that's &lt;em&gt;technically&lt;/em&gt; how we're supposed to think about the Java or C# world, but in those languages it ends up being a distinction without a difference - &lt;em&gt;particularly&lt;/em&gt; in languages which attempt to blend functional and object-oriented styles like Scala or C#. Not so in Smalltalk: everything reinforces and builds on the paradigm.&lt;/p&gt;
&lt;p&gt;A number of those folks with an affinity for Smalltalk insist that to this day it is still the &lt;em&gt;only&lt;/em&gt; object-oriented language. Whereas Smalltalk has an extremely light syntax laser-focused on its paradigm, the other languages have more robust - maybe &amp;quot;bloated&amp;quot; - syntaxes which, if not encouraging you to program &lt;em&gt;against&lt;/em&gt; the paradigm, do at least make it more difficult to stay focused. If you spend a reasonable amount of time with the language, you might run into a similar feeling I did. It takes a little bit of effort to get to the point that you &amp;quot;click&amp;quot; with the style of program that Smalltalk encourages, and when I got there I remember thinking &amp;quot;golly, this is &lt;em&gt;so different&lt;/em&gt; to object-oriented programming!&amp;quot; The kicker of course is that &lt;em&gt;this&lt;/em&gt; paradigm is (arguably) the One True object-oriented one, and divergences I make in other languages are maybe something a bit different.&lt;/p&gt;
&lt;p&gt;So I do think it will give you a great foundation of OOP in understanding how to craft a &amp;quot;pure&amp;quot; OO implementation, and I think that goes a long way to teaching what the fundamental goals of the paradigm are. Sure, we all had to read some paragraph about the paradigms in college, but that doesn't mean a whole lot in the face of finger-on-keyboard experience.&lt;/p&gt;
&lt;p&gt;The other envy you'll come away with is the live debugging ability - you're able to change programs as they're running and see those effects in real-time. If that's news to you then you might be surprised that that's true for an object-oriented language - isn't that supposed to be the thing that functional languages are good at? This is another brick in the &amp;quot;Smalltalk is the &lt;em&gt;only&lt;/em&gt; object-oriented language&amp;quot; wall - its message-sending focus allows the various components of the code to be decoupled so as to allow this. It's a great programming experience, though seeing as Smalltalk isn't used anymore I will confess it did serve to bias me towards functional languages which offer this capability today.&lt;/p&gt;
&lt;p&gt;Speaking of its use today, it's not used! At least, I can't really find a lot of documentation towards industry use. In fact, with as loved and useful as the language was, it's surprising that it didn't last that long. I don't have firsthand experience with this, but my understanding is that a fair amount of the legacy Smalltalk has already been rewritten. If you have knowledge about this, please do leave a comment! I think if you're pining for a high-paying job at a bank rewriting a legacy system (as every child dreams for when they grow up) you're probably still better off elsewhere. However, I put the following to you: if you go into an interview there's a nonzero chance yet you'll find yourself across the table from an experienced colleague with an affinity for Smalltalk, and I guarantee you'll be benefited there!&lt;/p&gt;
&lt;h1&gt;Prolog&lt;/h1&gt;
&lt;p&gt;Prolog is one of my favorites! I've not hidden that one of my two degrees is Philosophy, and when I was a student I focused quite a bit on logic. In fact, I was able to do a lot of my philosophy with prolog, which might be a bit of a head-scratcher if you haven't encountered logic programming before. This is a separate paradigm where you write programs by declaring logical statements of facts about your program. The computation is then done by an interpreter applying problem solving algorithms to resolve the relations of facts in the program.&lt;/p&gt;
&lt;p&gt;This is a decidedly different way of thinking about a program - one which you are unlikely to use developing most professional software. However, this skill of writing programs by description translates directly to a lot of tasks. Any database work maps 1-to-1, and it's the same part of your brain you need to engage when writing tests to reason about the system. Modern pattern matching algorithms are essentially doing the same thing as a prolog interpreter, just on a different level, so if nothing else you might be able to break out some slick pattern matching moves!&lt;/p&gt;
&lt;p&gt;This style of programming is quite influential in the field of AI, where it was largely employed back in the first AI boom in the eighties. It was also used, and to an extent still is, in natural language processing, data querying, and theorem proving. Is there a lot of &lt;em&gt;that&lt;/em&gt; sort of work being done at our normal 9-to-5 jobs? Unlikely. Is it the cool thing to be programming though? Absolutely, as it has been for some time. The style of programming supported by Prolog cuts through a lot of busy work and can get you going quite fast if you're looking for projects in these areas.&lt;/p&gt;
&lt;p&gt;One of the coolest ways to engage with Prolog though is to write your own interpreter for it. Heck, if you're so inclined it's somewhat trivial to write a parser for it. I know I introduced this article suggesting something other than making your own language, but I've had to sit through enough so-called experts tell me that Prolog isn't a real language so arguably this doesn't count. Being serious though, a parser and interpreter for it took me about 2 weeks of on-my-own-time effort, and that's really not bad. It was even fun ... towards the end!&lt;/p&gt;
&lt;p&gt;Now while that project might teach you the finer details of &lt;a href="https://en.wikipedia.org/wiki/SLD_resolution"&gt;SLD resolution&lt;/a&gt;, the primary emphasis I want to make is on how logic programming is beneficial, and for that you'll need to roll up your sleeves and dive into Prolog. However, there is an alternative: if you're more database or just data-inclined, then Datalog might be more your speed. It's a subset of Prolog that acts as a query language for databases. This use itself is a distinct paradigm for working with data, and its applications to more traditional SQL are more directly obvious.&lt;/p&gt;
&lt;p&gt;Prolog is one of the primary recommendations I make when I'm asked for &amp;quot;different&amp;quot; languages to learn, not just because it's so different from our normal bread and butter, but because the skills you gain from it apply in a lot of smaller but consequential ways. I think this gives Prologgers (I have no clue if that's what we're supposed to call ourselves) a leg up all around. Maybe it's the single best &amp;quot;old&amp;quot; language to learn to really round out a programmer. That is, in my humble opinion.&lt;/p&gt;
&lt;h1&gt;Fortran&lt;/h1&gt;
&lt;p&gt;Yes, I do mean &lt;em&gt;the&lt;/em&gt; Fortran created in 1958 that originally used punchcards, though you needn't use that old of a version! Would it surprise you to learn that its most recent version was released in 2023? Would it surprise you even more to learn that it still has a &lt;a href="https://fortran-lang.org/roadmap/"&gt;roadmap&lt;/a&gt;? You can load it up today in VS Code, intall dependencies from a package manager, and develop some really serious programs with it. Indeed, it's not just a use_able_ language, it is still used (I think more than the previous two).&lt;/p&gt;
&lt;p&gt;The applications for this language are sciency and mathy, to use technical terms. Being the low-level language it is, its uses are in that sort of computing that researchers run on supercomputers and the like. To support this, Fortran has strong support for array operations, efficient memory usage, and parallel computing. As such, today it's running practical computations in weather prediction, fluid dynamics, biology and chemistry simulations, and engineering modeling.&lt;/p&gt;
&lt;p&gt;Being one of (maybe &lt;em&gt;the&lt;/em&gt;) first science-focused language, a lot of its concepts provide the foundation for our newer languages in the same field, such as Python or R. As far as Fortran's practicability (i.e. &amp;quot;can I build a simple web API with it?&amp;quot;) I do genuinely get the sense that it is a lower-level R: they both have packages and fair documentation for doing modern application development. I'm unsure if either is able to be used, easily, for mobile application development. However, there are frameworks for each to support desktop and web application development, which helps greatly in eliminating any gap between the research and productizing (for lack of a better term) work that might need to be done on such projects.&lt;/p&gt;
&lt;p&gt;The skills I think you're most likely to pick up with this language are varied, and all at a low level. High-performance computing (HPC) is an obvious answer, but unless you've got easy access to a supercomputer that part might remain theoretical. Instead, practically, I'd suggest memory management and parallel computing. Sure, you can get a really good foundation in memory management with C, but I would suggest that Fortran's straightforward syntax is a better instructor on this subject. Parallel computing tends to become a more abstract concept the higher-level your language is, while I think Fortran would give you a relatively easy tool to explore that subject at the lowest level.&lt;/p&gt;
&lt;p&gt;I started toying with Fortran a year ago thinking it would be funny to be able to suggest creating microservices in the language at work, and I was surprised that I enjoyed the language so much. Today it's quite fashionable to use functional languages for the more mathy services that we need to write at our jobs. That used to be the domain of C or C++, but those have slightly fallen out of favor due both to the popularity of functional languages as well as concerns about the seemingly high presence of footguns in those languages. I'd suggest here that Fortran is a viable candidate to consider as well for these tasks - some computation is very well-served by the functional paradigm, but plenty of algorithms are best represented imperatively.&lt;/p&gt;
&lt;h1&gt;COBOL&lt;/h1&gt;
&lt;p&gt;Do not learn this.&lt;/p&gt;
&lt;h1&gt;Forth&lt;/h1&gt;
&lt;p&gt;Forth seems to have a perpetual, ebbing popularity - I see a bit of interest in it rise maybe every 5 years. That could just be me, but I think there's a good casual appetite for the language out there. Just like each of the previous languages, Forth operates in a separate paradigm to the rest. All programs in Forth run on a stack which holds all of the program data. Data is manipulated by popping it from and pushing results onto the stack, chaining stack operations together into a program.&lt;/p&gt;
&lt;p&gt;This has a couple of distinct advantages beyond solving the problem that we sometimes fight over what name a variable should have. The paradigm necessarily restricts programs to expressing concepts that map quite well directly onto the CPU operations while allowing higher levels of expression in the program code than other low-level languages. As such, it naturally rns quite fast with efficient memory use. The stack paradigm is particularly well-suited to state machines and expresses mathematical operations very neatly and composably. These facts all make it a great choice for firmware, robotics, and other sorts of embedded systems.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, it is still used for these tasks but quite rarely. There are plenty of newer stack-based languages as well, and those are maybe a bit more common in the toolbelt of the engineers working on these systems. That said, I have found (with admittedly limited experience) that all of these stack-based languages have the same core feeling, and Forth has excellent extensibility and pretty fair documentation, so I've stuck with this one.&lt;/p&gt;
&lt;p&gt;Like the logic programming paradigm of Prolog, this stack paradigm is one that can be replicated in any other programming environment. If your application has discrete, one-off requirements that can be served best with a stack-based system, you can surely find a good number of libraries in your language providing these capabilities without needing to include a Forth executable alongside your application. The best way to learn how to use this paradigm though, I suggest, is to properly learn a stack language.&lt;/p&gt;
&lt;p&gt;Being able to think primarily in terms of the stack has a lot of benefits on the side as well. Notably, I think most engineers use or have encountered Java or C# before, and the virtual machines which run each of these languages operate on a stack-based instruction set. There are plenty of ways that stacks are used creatively to boot, so I think it's safe to suggest that a robust understanding of them tends to pay off in the long term regardless.&lt;/p&gt;
&lt;p&gt;As a final note here, I'll mention also that Forth is particularly well-regarded for its extensibility - it's very easy to add new words to the language and develop domain-specific languages with them. There was a major push in favor of DSLs in the last decade which doesn't seem to have taken off, but in the abstract their concept applies to all the programming we do. When we create a library or implement a standard for a layer in any application, we're defining a DSL &lt;em&gt;of sorts&lt;/em&gt; which is defined by the constraints and standards we develop for that codebase. There are a few languages which are particularly biased towards this DSL-style thinking, of which Forth is one. This is a completely separate sort of thinking than stack thinking, though nonetheless hugely beneficial for any programmer. Though, I wonder if that's a separate article in the future!&lt;/p&gt;
</description>
      <pubDate>Fri, 20 Sep 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-09-20T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">looking_beyond_github</guid>
      <link>https://ian.wold.guru/Posts/looking_beyond_github.html</link>
      <title>Looking Beyond GitHub</title>
      <description>&lt;p&gt;Like most folks in our industry, I've been using GitHub extensively for a long time, and I've become very familiar with it (and, as a consequence, Git). For the most part, I do like doing version control in Git. There's a number of efforts afoot to try to propose alternate VC systems that solve common issues with Git, but I don't really see these gaining a huge traction. Then again, there's a lot of folks reevaluating a lot of common practices these days.&lt;/p&gt;
&lt;p&gt;GitHub is a huge advantage gained for using Git: the tool makes it incredibly easy for me to host public and private projects, with all the CI bells and pages whistles I need. Nonetheless, I'm going to be working on getting myself off of GitHub over the next several months. Instead, I'll be spinning up my own &lt;a href="https://about.gitea.com/"&gt;Gitea server&lt;/a&gt; on Railway. Why? Ownership, cost, and control.&lt;/p&gt;
&lt;p&gt;I have a very significant amount of code in GitHub: some public, mostly private, some in between. GitHub has fine features to facilitate collaboration on code, but nothing so granular as what I can do with my own server. Controlling code access is one thing, but being able to have easier control over my CI/CD infrastructure is also appealing. I might be getting some cost saving in some areas, but more importantly it's easier to set up custom infrastructure with my own server than fooling around with GitHub.&lt;/p&gt;
&lt;p&gt;At an even higher level though, being able to have ownership of your own things is good these days. It's &lt;a href="https://docs.gitea.com/installation/install-with-docker"&gt;devastatingly simple to spin up a Gitea&lt;/a&gt;, committing me to not much more time than I normally would put into maintaining my GitHub while gaining a bit more platform independence. Lately I've been a huge advocate for owning your own tools; I think this mirrors my preference to roll your own code where practical. Increasingly, everything about our lives is becoming a subscription service requiring us to pay for the privilege of owning less - how exponentially impoverished we're becoming! Ownership isn't now about wealth; it's a matter of freedom.&lt;/p&gt;
&lt;p&gt;So I'll be spinning up my own Gitea server, migrating off GitHub, and gaining a huge benefit - by my eyes. There remain issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If I delete my GitHub, how do I find others' repos? How do they find mine?&lt;/li&gt;
&lt;li&gt;Gitea doesn't have an equivalent to Pages, but obviously this can be done through Railway. Need to figure a good architectural approach.&lt;/li&gt;
&lt;li&gt;Railway, funny enough, is built entirely around &amp;quot;give me a GitHub repo and we deploy that with no effort.&amp;quot; There will be more effort now.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These problems will be ironed out, and I'll document on my blog how I get through these. The first point is a sticky one though: the industry is so used to being on GitHub. That's not as hard an expectation now as it was five years ago, but lacking any separate protocol for repo discovery this may be interesting. For public projects I might set up some kind of GitHub replication. Goodness knows!&lt;/p&gt;
</description>
      <pubDate>Sat, 21 Feb 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-02-21T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">many_dimensions_of_heterogeneity</guid>
      <link>https://ian.wold.guru/Posts/many_dimensions_of_heterogeneity.html</link>
      <title>Many Dimensions of Heterogeneity</title>
      <description>&lt;p&gt;The eighth and final fallacy of distributed computing is &amp;quot;the network is homogenous,&amp;quot; originally referring to network hardware. The caution at the time was to not overlook the challenges in dealing with different physical routers, switches, servers, and the like when dispatching requests across a network.&lt;/p&gt;
&lt;p&gt;Over time software protocols have been able to alleviate most hardware concerns for developers of modern distributed systems, so the fallacy is more often used now to caution that the &lt;em&gt;software&lt;/em&gt; dimension is heterogeneous. Changing topologies, different protocols or message formats, and evolving network configurations and policies are some examples in this dimension. As components of distributed systems are increasingly tended to by greater numbers of administrators, the degree of network heterogeneity at the software level increases.&lt;/p&gt;
&lt;p&gt;The next dimension above this is maybe the most interesting one - architectural heterogeneity. This would encompass everything from differences in the semantics of contracts and business objects to persistence and consistency models to SLAs (service level agreements). This dimension is not frequently touched on in discussions regarding the fallacies of distributed computing, and I think that's a shame. I typically think of a distributed system being more difficult and/or complex than a monolith by a factor of 10 in &lt;em&gt;every&lt;/em&gt; aspect - 10x more surface area for bugs, 10x as many considerations to guard against, 10x as much development time required, etc. It occurs to me then that there would be 10x as much architectural burden from these systems, and the heterogeneity of the constituent parts of the system is directly contributing to that.&lt;/p&gt;
&lt;p&gt;Having already discussed most technical aspects of the fallacies in my other articles on the matter, here I want to focus on the various dimensions in which distributed systems are heterogeneous. Just as standardized abstractions and a defensive attitude are able to stave off the complications which were initially observed stemming from hardware heterogeneity, I think we'll find the same approach will adapt well to these other dimensions.&lt;/p&gt;
&lt;h1&gt;The Software Dimension&lt;/h1&gt;
&lt;p&gt;If we review my previous articles on distributed computing, we'll quickly find that distributed systems often do not - nay, typically do not - have homogenous &lt;a href="https://ian.wold.guru/Posts/lateny_is_zero.html"&gt;latency&lt;/a&gt;, &lt;a href="https://ian.wold.guru/Posts/bandwidth_is_infinite_ly_troublesome.html"&gt;bandwidth&lt;/a&gt;, &lt;a href="https://ian.wold.guru/Posts/the_network_is_secure.html"&gt;security&lt;/a&gt;, &lt;a href="https://ian.wold.guru/Posts/topology_doesnt_change.html"&gt;topologies&lt;/a&gt;, nor &lt;a href="https://ian.wold.guru/Posts/there_is_one_admin.html"&gt;administration&lt;/a&gt;. We've well-explored the software causes and solutions to these in the past. There are plenty of software-level concerns apart from these networking ones to explore.&lt;/p&gt;
&lt;h2&gt;Protocols and Formats&lt;/h2&gt;
&lt;p&gt;These are, of course, the fundamental building blocks of any kind of distribution scheme. I need a protocol for the client and server to understand to transact packets, and those packets need to be formatted in a mutually intelligible manner. I think it's fair to say that today we have a healthy set of each to choose from which are well-defined and well-established. IP, DNS, HTTP(S), FTP, TCP, Websockets; XML, JSON, Protobuf; I needn't list them all but you get the picture. If we're building a brand new system today we're probably going to fall back on the good 'ol DNS, HTTPS, JSON, UTF-8, and the like - you know the drill.&lt;/p&gt;
&lt;p&gt;All of our languages, frameworks, and tooling supports this stack natively and of course also supports the other common protocols and formats. But we'll use the familiar defaults and then we won't need to worry about differences right? Well, just a decade ago (maybe a decade and a half? Surely not two decades &lt;em&gt;yet&lt;/em&gt;? Time flies...) a lot of us were thinking that SOAP and WSDLs and XML was the greatest thing since sliced bread and it would be great into the future. Visual Studio could generate a client so easily! So that didn't pan out, and now a lot of us need to write code to integrate with these systems we now call &amp;quot;legacy&amp;quot; because we don't like WSDLs today.&lt;/p&gt;
&lt;p&gt;Popular protocols and formats move on, and this introduces multiple protocols into our systems. We can also inherit them if we absorb another company's system into our own (takeover, merger, etc). Sometimes our services even have technical requirements to differ! It's the same with formats, where XML gave way to JSON and then for a few months &lt;a href="https://www.infoq.com/news/2023/07/linkedin-protocol-buffers-restli/"&gt;everyone wanted to switch to Protobuf&lt;/a&gt;. Versioning is especially fun too - HTTP/1.1 differs from HTTP/2 &lt;a href="https://www.digitalocean.com/community/tutorials/http-1-1-vs-http-2-what-s-the-difference"&gt;in a lot of ways&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nowadays we tend to do a pretty good job, when using HTTP, to pass necessary information along with the headers - this data is JSON, it's UTF-8, and so on. I've yet to see a header though detail what format the dates are though, and in spite of my best efforts I still run into date (de)serialization issues every now and again.&lt;/p&gt;
&lt;p&gt;There's a whole lot of detail in these, and assuming two services are 100% in-line with each other - even if they agree HTTP, JSON, and the like - is to commit this fallacy. Indeed, this is perhaps the area of the greatest heterogeneity in our systems, even though we might not notice all of it. In a few paragraphs I'll discuss the semantics of contracts, and it's in these contract negotiations that this can all be hashed out. While you're arguing whether the API needs to return the created date for an object, you should also be having the conversation of what that (or any) date looks like. The increasing standardization of our protocols and formats is welcome, as is the tendency for our tools to become more resilient with each other as a result. None of this saves us from the fact that these differences exist. At that, they don't just &lt;em&gt;possibly&lt;/em&gt; exist, they very well do in each of our systems!&lt;/p&gt;
&lt;h2&gt;Software Stacks&lt;/h2&gt;
&lt;p&gt;It's been said quite a bit about microservices - less and less lately now that the trend is subsiding - that one of their great advantages is the ability for separate teams to use different technology stacks - even different languages - in building out their individual services. Certainly this is a &lt;em&gt;significant&lt;/em&gt; side effect of the architecture, thogh depending on what kind of organization you're working for this can be significantly good or significantly bad. The idea of watching the ripple effects across a department of 50 C# engineers slowly coming to the realization of the implications of one team having gone rogue in deploying a Clojure gRPC service is an entertaining one.&lt;/p&gt;
&lt;p&gt;Not that I've ever observed such a phenomenon.&lt;/p&gt;
&lt;p&gt;But here you might say: &amp;quot;Well Ian, this is simple! We can have some organizational coding standards, enforce C# and some common Nuget libraries, and avoid the concern!&amp;quot; If this is you, then it is you for whom I am writing this article, for this line of reasoning is to fall victim to one of the classic blunders: &lt;em&gt;it is this fallacy!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Separate teams deploying separate services will diverge and have separate tech stacks. Separate tech stacks communicating with each other will have inconsistencies. Tautologies are always true. Being clear, this will always be a bug vector, but to be aware of this fact is to minimize the surface area of the vector. Do vectors have surfaces? I'm bad at geometry.&lt;/p&gt;
&lt;p&gt;This usually crops up in small things. Do the two separate systems have the same ability to parse dates? Do the email validations follow the same rules? Do you have addresses? Do you have international addresses? Do you have a button on your site that says &amp;quot;I know you weren't able to parse this address but keep the one I entered&amp;quot;? My disdain for handling addresses aside, just in data interpretation alone there are so many points for slight divergence between different software systems - different libraries, languages, configuration defaults thereof, and so on.&lt;/p&gt;
&lt;p&gt;Let's throw some load balancers, firewalls, and caches on top. What kinds of request policies do these expect of your client? Is the client service capable of acting in a manner friendly with this? You'd expect so, but the point is to not assume so. All of these concerns are, in theory, wrapped up into the contract negotiated between the two entities. And how many contract negotiations have happened that excluded, uh, &lt;em&gt;most&lt;/em&gt; of the things it should include? Haha, this article doesn't seem so pointless all of a sudden!&lt;/p&gt;
&lt;p&gt;This bleeds nicely into the next dimension: if we can't trust the components of a distributed system to be architecturally homogenous with each other, can we at least trust the network itself to be architecturally homogenous?&lt;/p&gt;
&lt;h1&gt;The Architecture Dimension&lt;/h1&gt;
&lt;p&gt;To immediately answer the above question: no, of course not!&lt;/p&gt;
&lt;p&gt;For a long time I disregarded the eighth fallacy - the network is homogenous - as its original sense doesn't really apply as much any more. It gained a new life for me in this more abstract sense having read &lt;a href="https://particular.net/blog/the-network-is-homogenous"&gt;David Boike's article for Particular&lt;/a&gt; on this same fallacy. It's unfortunate that article is so short because there's plenty more to be said regarding architectural heterogeneity.&lt;/p&gt;
&lt;h2&gt;Semantics: Objects, Actions, and Contracts&lt;/h2&gt;
&lt;p&gt;Now you might be tempted to dismiss this as just a difference in semantics (hahaha ... please laugh) but this really does matter. In fact, semantics is a lot of what we do in modeling the business, whether we know it or not. Ensuring consistency between our semantics is paramount to ensuring a correct mapping of the business domain. Misunderstandings - even at a fine-grained semantic level, is an ever-present vector for bugs.&lt;/p&gt;
&lt;p&gt;I currently work in ecommerce, so I can take the business object &amp;quot;item&amp;quot; to demonstrate semantic difference. Items exist, obviously, all over the place on a shop website. You can view an item, maybe customize it with some options, and you can add it to a cart or a registry. This involves at least three teams: an items team, a cart team, and a registry team. Does item mean the same thing to all these teams?&lt;/p&gt;
&lt;p&gt;Let's say the items team is tasked with maintaining a service that stores all the items; this is the source of truth for what the &amp;quot;Luxury Dinner Plate&amp;quot; and other items are. Their service lets you query the details for this item and produces events when it becomes no longer available and the like. To the items service, that's what an item is: It has details (name, price, personalization options) and can go in and out of availability, and it only ever has a single &amp;quot;Luxury Dinner Plate&amp;quot;.&lt;/p&gt;
&lt;p&gt;But now take the cart or the registry. Those can have multiple &amp;quot;Luxury Dinner Plate&amp;quot;s in them, maybe with different personalization options. Therefore you'd expect the cart and registry services to have different identifiers for their items than the item service; an item for these other services is actually an &lt;em&gt;item in cart&lt;/em&gt; or &lt;em&gt;item in registry&lt;/em&gt;. Hopefully it's clear then the importance that when these teams work together (and consequently when the services interact with each other, directly or indirectly) that everyone understands the semantic difference. For quick example, it would be insufficient for an &amp;quot;item shipped&amp;quot; event to only include the identifying item information used by the item service but exclude information required by the registry service.&lt;/p&gt;
&lt;p&gt;That example might or might not be a recent problem I had to tackle.&lt;/p&gt;
&lt;p&gt;This naturally extends beyond the objects and their properties to the sorts of actions taken on an object. Of course you can't &lt;em&gt;purchase&lt;/em&gt; an item stored in the item service, you can only purchase an item stored in the cart service. If you feel compelled to interject here and try to correct me with &amp;quot;but you can also &lt;em&gt;purchase&lt;/em&gt; an item from the registry service!&amp;quot; you'd be wrong! Haha, got you! You cannot, but you can &lt;em&gt;add to cart&lt;/em&gt; an item from the registry service. &amp;quot;Oh but come on,&amp;quot; I can hear you objecting, &amp;quot;that's just a semantic difference!&amp;quot; &lt;em&gt;Exactly!&lt;/em&gt; And if it's left unconsidered you're going to end up with a real fun issue in production.&lt;/p&gt;
&lt;p&gt;These two aspects - what an object are and what its actions are - are the basis of contracts between components in the system, and their semantics form part of the semantics of the contracts. I'm being very careful to not accidentally assert that they are &lt;em&gt;all&lt;/em&gt; of what the contracts are. No, unfortunately the real world is more complicated, but they're the two halves of domain modeling and consequently I think that object and action semantics are the two main bug vectors  from the contract/semantic side. The hope is that this impresses the importance of thoroughness in negotiating and defining these contracts. If you encounter a colleague accusing you of getting too granular or &amp;quot;semantic&amp;quot; about it, remember that &lt;em&gt;that's the whole point&lt;/em&gt;!&lt;/p&gt;
&lt;h2&gt;Persistence and Consistency Models&lt;/h2&gt;
&lt;p&gt;That a distributed system would have a mixture of persistence and consistency models is, I think, an absolute certainty. If you're not dealing with enough data or transactions to have to soften data requirements &lt;em&gt;somewhere&lt;/em&gt; then your application is probably a poor candidate for distribution.&lt;/p&gt;
&lt;p&gt;Persistence models mostly affect individual services while consistency models apply across the system. Ideally components of a distributed system are isolated and one needn't know about the internal function of another (persistence being an &amp;quot;internal function&amp;quot;), though these two do affect each other; different persistence models can allow a service to support a given consistency model more or less easily. A hard persistence model such as &lt;a href="https://en.wikipedia.org/wiki/ACID"&gt;ACID&lt;/a&gt; ensures a service can maintain data integrity and best supports a hard consistency model, while at the other end a soft soft models such as &lt;a href="https://www.geeksforgeeks.org/acid-model-vs-base-model-for-database/"&gt;BASE&lt;/a&gt; are entirely contiguous with the softer consistency models.&lt;/p&gt;
&lt;p&gt;ACID and BASE, when applied to database transactions, are typically only describing persistence within a discrete service - a single actor against the database. When we move up a level to consider separate pieces of related data in a system, we might find that we need to maintain data integrity across distributed systems, and we might find ourselves tempted to consider support for single transactions distributed across these components. My advice is to avoid distributed transactions at all costs; it is typically an antipattern. If you encounter the need for them this is a smell which indicates that you should either combine the components which share the related data, or you should instead rearchitect the system to accept softer consistency in that data. If you really do need distributed transactions though then use a single database which &lt;a href="https://www.postgresql.org/docs/current/two-phase.html"&gt;supports such transactions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The overall trend though is that distributed systems will result in components with softer persistence models, which in turn results in softer consistency models across the system. I think it would be difficult for a distributed system to avoid having any eventually-consistent components. Considering the technical constraints which &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;we explored with the other fallacies&lt;/a&gt;, asynchronous communication is an essential tool in solving some of the more difficult technical issues. Not only does that require accepting eventual consistency, but it circles back to having to accept a soft presistence: packets might get dropped without the downstream system being any the wiser. State drift!&lt;/p&gt;
&lt;p&gt;For an example that demonstrates these effects, consider the ecommerce system from before with the items, cart, and registry services. Suppose that to save having too many calls into the items service, we update the items service to send out an event when data about the item changes (let's say name and price), and asking consuming systems to listen to these events and record what they need themselves. It's easy to see a potential issue here: suppose an event goes out and it is received by the cart service but not the registry service: you might get a situation where a customer sees the &amp;quot;Luxury Dinner Plate&amp;quot; for $10 in a registry, adds it to their cart, and then sees it as $12 in the cart because the registry service never got the event that updated the price.&lt;/p&gt;
&lt;p&gt;This seems like a glaring issue with event-driven systems, but the key is on data requirements. Perhaps an item price is a poor choice for such a setup, since many systems have a hard requirement to be aligned on that data. How about the name of an item though? That's probably only going to change if there's a typo; this is an excellent candidate to be updated via an asynchronous channel. We can say that item name can accept softer persistence and consistency models, while the item price does not have that luxury.&lt;/p&gt;
&lt;p&gt;We do have other options with the item price though. While we might need to accept a very hard persistence model for the item price data, we might be able to identify some components in the system that do not need to have as hard a consistency with item price data than other components. That is to say, some components need &lt;em&gt;real-time&lt;/em&gt; item prices, while others would be satisfied with only &lt;em&gt;near-real-time&lt;/em&gt; prices. For those latter components, we might be tempted to replicate the item price data geographically so that requests to the item data can still have high throughput with minimal strain on the broader of the network. In such a scheme, the item service would expose two separate interfaces for the real-time and near-real-time flows; the former requests would query against the main dataset (the one which is always written to from which the other replicas copy) guaranteeing real-time data, while the latter requests would query against the closer, faster replica data. Feasibly the items service could be broken out so that the item price data is managed by a separate service, considering the different architectural requirements for this data.&lt;/p&gt;
&lt;p&gt;To bring it back around, we can see that different components of the sytem will require different persistence models, and this has a knockon effect of supporting many different (though potentially &lt;em&gt;similar&lt;/em&gt;) consistency models. That becomes a cycle where consistency models will influence individual persistence models, with the asterisk in the whole cycle being that business requirements will set lines in the sand as to how far in any direction the system can move here. Components with different models will interact with each other in interesting ways, which makes this a crucial area requiring communication and understanding. Even between two services which each describe their consistency model in the same way, to assume they really are entirely the same is a potential flaw.&lt;/p&gt;
&lt;h2&gt;SLAs and QoS&lt;/h2&gt;
&lt;p&gt;One of my favorite observations to come out of the microservices milieu is that most microservices, in most microservice setups, don't implement proper failover techniques. Or, they don't implement them at all. This is to say that if Service A depends on Service B then Service A really &lt;em&gt;should&lt;/em&gt; be able to serve requests with &lt;em&gt;some kind&lt;/em&gt; of meaningful response (other than 502) when Service B is down, but this seems to be implemented infrequently. If we expand this to a system containing many services which are probably not isolating themselves from downstream failures, it's easy to see the potential for cascading failures; these might be incurred by network errors or low fault tolerance. Indeed, when my service depends on another it's difficult to figure how to get around that dependency when it is down. This is often where it's beneficial to be able to grasp the wider scope of the system and ensure &lt;em&gt;its&lt;/em&gt; architecture supports the kinds of services which are able to do that.&lt;/p&gt;
&lt;p&gt;We can see there's two aspects (beyond architecting the system and coding the services correctly) which influence the fault tolerance architecture of a service. The first is how these aspects are included in the contract(s) the service has with its client(s): what uptime is guaranteed, average response time targets, and so on. This is the SLA, or Service-Level Agreement. The second aspect is the relationship the service has with the network: different services have different performance and network-usage characteristics requiring different provisions of network resources. This is the QoS, or Quality of Service. Think of SLAs as keeping up the business functionality and QoS supporting the overall network functionality. These are two aspects which should be analyzed, have standards agreed, and monitored. To be clear though, this doesn't need to be an overly formal thing if you're supporting a simpler system - but because you &lt;em&gt;are&lt;/em&gt; monitoring your services (right?) then you should be setting metrics for what you expect to see at the least.&lt;/p&gt;
&lt;p&gt;The key point to be made here, of course, is that the SLA and QoS goals are going to be different for each component in the system. Having the targets for each &lt;em&gt;defined&lt;/em&gt; are what allow me to plan for those differences; actually implementing strategies for this heterogeneity is the next step. The implementation is a continuous action of monitoring and adjusting. You'll adjust your architecture to support different techniques for failover and fault tolerance and you'll adjust your network to accommodate the many different requirements. Just as well that we haven't fallen on the assumption of homogeneity here - look at what we can do!&lt;/p&gt;
&lt;p&gt;Managing the QoS is about shaping the network traffic - something that we're well-familiar with having reached the end of our series on the fallacies. This involves identifying where we can apply traffic shaping mechanisms, rate limiting, and the like. There's lot of ways of going about finding these; it suffices to say that you should be logging information from your services which help identify traffic patterns. Relying on the OpenTelemetry standard can allow you to analyze these with very good tools like &lt;a href="https://www.honeycomb.io/"&gt;Honeycomb&lt;/a&gt;. If all of your traffic passes through a gateway, firewall, or other central component (which is a &lt;em&gt;questionable&lt;/em&gt; practice depending on what that central component is) then you can get some very fine-grained monitoring here.&lt;/p&gt;
&lt;p&gt;Of course since you've got your monitoring set up it's much easier to enforce your SLA targets; most tools can set up alerts if your uptime falls below 99.99-whatever percent and the like. I'm a big fan of having these alerts. If a part of the system goes offline it's nice to have somebody get texted to address the issue. During a failure (which you could feasibly simulate) you'll also be able to see whether it cascaded to parts of the system you might have thought were isolated. I know I keep coming back to it, but if you haven't I really encourage you to read the rest of &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;my series on the fallacies&lt;/a&gt; - I've already covered a lot of fault tolerance strategies so there's a lot I'm leaving out here.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I think the whole idea I want to convey is that every component of a distributed system is different from every other component, not just in the obvious ways but in &lt;em&gt;every&lt;/em&gt; way. These differences might be small and subtle but they are there, and the most that these differences are acknowledged, discussed, coded for, and monitored around, the better. These differences crop up in a whole host of dimensions. There's the hardware dimension which I didn't discuss here, but then the software and architectural ones.&lt;/p&gt;
&lt;p&gt;The focus of course is on those aspects which are often overlooked. We might forget about protocol differences because &lt;em&gt;of course everyone uses HTTP, right?&lt;/em&gt; Or then we forget that an &lt;em&gt;item&lt;/em&gt; is not an item when it's inside a &lt;em&gt;cart&lt;/em&gt;. Often times these are problems which are easily resolvable once encountered, but sometimes they bring the whole show to a halt.&lt;/p&gt;
&lt;p&gt;If we are to create truly robust distributed systems, we need to spend the time upfront to ensure our development is on the right track here, otherwise we expose ourselves to a great many more bug vectors, slowing development in the best case but impacting our users and businesses in the worst cases.&lt;/p&gt;
</description>
      <pubDate>Fri, 25 Oct 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-10-25T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">minneapolis_january_2026</guid>
      <link>https://ian.wold.guru/Posts/minneapolis_january_2026.html</link>
      <title>Minneapolis, January 2026</title>
      <description>&lt;p&gt;If you've visited my blog before, you know that I live in Minneapolis. I state that on my about page, my now page, my GitHub profile, and in plenty of articles I've written on this blog. I've always loved this city, and I love living here.&lt;/p&gt;
&lt;p&gt;Unless you've been living under a rock for the last month, you've seen that my city is in an interesting way right now. Several colleagues have reached out to me in the last month to check in on me. That's been very much appreciated. I am alright, considering.&lt;/p&gt;
&lt;p&gt;There's a lot of folks in the Twin Cities that aren't alright though. In case anyone reads my blog from afar and feels a need to help out in any way, I want to encourage you to visit &lt;a href="https://www.standwithminnesota.com/"&gt;standwithminnesota.com&lt;/a&gt;. There are plenty of ways to help, financially and otherwise.&lt;/p&gt;
&lt;p&gt;I make a point of (largely) avoiding political conversation on my professional software engineering blog. I will stress that we must not consider discussion about ICE's activity in Minneapolis to be &amp;quot;political;&amp;quot; the totality of what the federal government has fashioned here is another thing entirely.&lt;/p&gt;
&lt;p&gt;Thanks again to all my colleagues for your care and support!&lt;/p&gt;
</description>
      <pubDate>Sat, 31 Jan 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-01-31T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">monokai_gray</guid>
      <link>https://ian.wold.guru/Posts/monokai_gray.html</link>
      <title>Monokai Gray</title>
      <description>&lt;p&gt;About a week (?) or so ago I hashed out a quick Sublime Text color scheme called Monokai Gray. I did so because I desperately love Monokai and I love the &lt;a href="https://sublime.wbond.net/packages/Wombat%20Theme"&gt;Wombat Theme&lt;/a&gt; as well. Unfortunately, I couldn't use Monokai with Wombat (without throwing up) because the yellowey hue of Monokai clashed with the classy grays of Wombat. Truthfully, I had been using a similar color scheme in Visual Studio for some time (the yellowey background didn't fly there, either). So I copied many of the colors over and prettied it up a tad, and this was the result.&lt;/p&gt;
&lt;p&gt;Here's a preview of what it looks like with some code I got &lt;a href="https://wiki.python.org/moin/SimplePrograms"&gt;here&lt;/a&gt; using the aforementioned Wombat Theme:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://bit.ly/MonokaiGraySample1" alt="Monokai Gray" /&gt;&lt;/p&gt;
&lt;p&gt;If you think it's neat, you can &lt;a href="https://sublime.wbond.net/packages/Monokai%20Gray"&gt;check it out&lt;/a&gt; on Package Control. I love feedback and pull requests alike!&lt;/p&gt;
</description>
      <pubDate>Thu, 05 Dec 2013 00:00:00 Z</pubDate>
      <a10:updated>2013-12-05T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">my_continuing_descent_into_madness</guid>
      <link>https://ian.wold.guru/Posts/my_continuing_descent_into_madness.html</link>
      <title>My (Continuing) Descent Into Madness</title>
      <description>&lt;p&gt;It wasn't that long ago that I tasked myself with updating a few microservice codebases to be able to work on VS Code along with Visual Studio. I really don't like the stock VS Code, so I spent a few hours customizing the colors and moving the explorer to the right. Then I added some custom keybindings so that I could navigate around the IDE efficiently.&lt;/p&gt;
&lt;p&gt;Being clear, VS Code is not the perfect IDE. In fact, there's many things for which VS is better. VS Code is an Electron app, and you can tell even just while typing in it - VS is faster at typing. However, a combination of the customizability and several quality of life features, I became a bit envious of my colleagues who were using VS Code full-time.&lt;/p&gt;
&lt;p&gt;So I decided to try using VS Code full-time.&lt;/p&gt;
&lt;p&gt;Oh, how I should not have started to tweak my environment. Once I opened the first door I've ended up opening them all, tweaking every aspect of my digital life.&lt;/p&gt;
&lt;p&gt;At this point I've completely riced my VS Code. I have custom CSS (via &lt;a href="https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css%5D"&gt;Custom CSS and JS Loader&lt;/a&gt;) to tweak each little nitpick I have. I've remapped almost all of the keybindings and added plenty of my own. When I use stock VS Code I feel dirty and disgusted, and when my colleagues use my VS Code they feel dirty and disgusted.&lt;/p&gt;
&lt;p&gt;But this wasn't enough for me, I'd found a zeal for getting the most out of my environment, for having every command at my fingertips, for elminating visual clutter from my screen.&lt;/p&gt;
&lt;p&gt;I found myself increasingly, then exclusively, relying on &lt;code&gt;ALT+TAB&lt;/code&gt; to navigate windows. I'd keep my IDE maximized (side note: I should write a script to hit F11 when it starts up), then I started keeping my browser maximized, then everything else. The Windows task bar was just getting in the way now - I took all the pinned applications off it and made Windows hide it from me.&lt;/p&gt;
&lt;p&gt;I don't keep too many windows open at any given time, but &lt;code&gt;ALT+TAB&lt;/code&gt; takes its toll. Wouldn't it be faster if I could map &lt;code&gt;ALT+&amp;lt;Number&amp;gt;&lt;/code&gt; to a window, or an environment?&lt;/p&gt;
&lt;p&gt;The envy I once had for my VS Code colleagues has been replaced by an envy for my Linux colleagues who can use i3 or Qtile. I needed a tiling window manager, but on Windows? In fact, &lt;a href="https://github.com/glazerdesktop/GlazeWM"&gt;yes you can&lt;/a&gt;, yes I did, and yes I riced the hell out of it.&lt;/p&gt;
&lt;p&gt;Not only could I have separate workspaces one &lt;code&gt;ALT+&amp;lt;Number&amp;gt;&lt;/code&gt; away, windows are maximized by default! If you've been using a tiling window manager for some time you know this is routine, but I was new to this world and it only fueled the fire I'd found myself in. My coworkers now joke when I'll switch to Linux. They think it's funny, I fear it might be inevitable.&lt;/p&gt;
&lt;p&gt;I would need icons for my workspaces of course, and Glaze recommends using &lt;a href="https://www.nerdfonts.com/"&gt;Nerd Fonts&lt;/a&gt; for this. I'd resisted installing one of these for so many years. What's wrong with good old Consolas? Am I going to become one of those dweebs with all the glyphs in their code?&lt;/p&gt;
&lt;p&gt;Yes, yes I did. And I have pretty icons for my workspaces. And once you give an engineer a nerd font I suppose it's only a matter of time (hours, in my case) that &lt;a href="https://ohmyposh.dev/"&gt;Oh My Posh&lt;/a&gt; sneaks its way in. My Powershell is now gorgeous.&lt;/p&gt;
&lt;p&gt;Like a frog in boiling water, this has all happened to me gradually and I barely registered this transformation. I would come across an extension like &lt;a href="https://marketplace.visualstudio.com/items?itemName=Katsute.code-background%5D"&gt;Background&lt;/a&gt; for VS Code and install it, not thinking twice that now all of my code has pretty space images behind it. But I need to acknowledge it now, lest you too might fall into this trap.&lt;/p&gt;
&lt;p&gt;I've gone mad and I see no end in sight. Will I start using Arch? Will I switch to Neovim? Will I take a job writing Haskell whitepapers? I do not know.&lt;/p&gt;
</description>
      <pubDate>Sat, 09 Dec 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-12-09T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">on_task_priority</guid>
      <link>https://ian.wold.guru/Posts/on_task_priority.html</link>
      <title>On Task Priority</title>
      <description>&lt;p&gt;I don't know of a lot of successful software projects that don't have some sort of list of tasks defined. Unless you've found some magical, outlying case, we need to define our work before we can do it. This usually takes the form of a kanban board where tasks are &amp;quot;cards&amp;quot;, or at the very least tasks are defined in rows in a spreadsheet.&lt;/p&gt;
&lt;p&gt;Naturally, these tasks have priority. This isn't just useful so that I know which task to pick up next when I'm done with my current one - we usually have milestones or objectives we need to meet in our development and organizing that gives priority to the tasks. Tasks and priorities are absolutely necessary, so we slap a tshirt size on our cards, keep them ordered with higher priority at the top of the list, and consistently reevaluate priorities to keep everyone aligned, right? Right?&lt;/p&gt;
&lt;p&gt;The trouble, as with most things in software engineering, comes from the fact that this field is filled with software engineers. Maybe one day we'll be able to do something about that root cause, but until then we'll have to deal with it. In this case it's that we software engineers can't help but organize, systematize, and insert-your-word-here-ize every problem we find. We do tend to over-engineer, and boy is task priority the sort of thing we tend to over-engineer. I want to take a look here at the best practices.&lt;/p&gt;
&lt;h1&gt;Maintain Alignment&lt;/h1&gt;
&lt;p&gt;The priority of a task is directly tied to the definition of the task and the goals of the project, and none of the variables are static. They change by the day, and sometimes by the hour. Each of these three are essential for an entire team to understand about a project, and everyone needs to be on the same page. This is the most important thing to recognize about task priority.&lt;/p&gt;
&lt;h2&gt;Reevaluate Often&lt;/h2&gt;
&lt;p&gt;Every single time we meet with stakeholders, the project changes. Sometimes dramatically. This is a feature, not a bug; we want to have the tightest loop we can between delivering value and incorporating feedback. This feedback always changes priorities. Features get reconsidered, bugs get discovered, milestones move.&lt;/p&gt;
&lt;p&gt;The ramification is that priorities change to adapt to this, and as long as priorities are being documented they need to be revisited and revised when these parameters change. This is an opportunity to make sure the stakeholders and the team are aligned. Ask questions in terms of priority, and document and clarify &lt;em&gt;why&lt;/em&gt; tasks have the priority they do.&lt;/p&gt;
&lt;h2&gt;Organize Tasks by Priority&lt;/h2&gt;
&lt;p&gt;This is as simple as keeping them at the top of whatever shared list the team has. It seems obvious to read, but it's often overlooked. How many times have we found an errant &amp;quot;high&amp;quot; priority task that's been at the bottom of a backlog for twenty sprints?&lt;/p&gt;
&lt;p&gt;Reorganization should be part of reevaluation. Often times tasks will be assigned one of a small number of priority options; in this case there are several tasks with &amp;quot;high&amp;quot;, &amp;quot;medium&amp;quot;, or whichever priority labels you have. This doesn't tell the whole story though - the team might have different goals this week or sprint. Maybe some milestone is more pressing and its related tasks should be considered first. These fine-grained priority assignments are best encoded and documented by keeping the task list in order of priority.&lt;/p&gt;
&lt;h1&gt;Assign Meaningful Priority&lt;/h1&gt;
&lt;p&gt;Priority has meaning to everyone - the stakeholders, team members, and even users sometimes. All of these people need to be kept in alignment about priority, and so some kind of common language is needed. The priority needs to be meaningful to everyone involved, and the meaning needs to be the same for everyone across the board. This is extremely difficult to achieve since very few of us have developed adequate skills in telepathy.&lt;/p&gt;
&lt;p&gt;The most important thing is to keep priority limited to a very small number of generic words. 3-5 is ideal. If you're able to get away with just 2, that's spectacular. Consider that each additional priority category &lt;em&gt;doubles&lt;/em&gt; the chance for confusion for each individual that needs to be aligned on priority. If you have 15 priority categories ranging from &amp;quot;no priority&amp;quot; to &amp;quot;EVERYONE SWARM NOW&amp;quot; then nobody is going to understand anything, math being what it is (trust me, I ran the numbers).&lt;/p&gt;
&lt;h2&gt;&amp;quot;Highest&amp;quot; and &amp;quot;Lowest&amp;quot; Have no Meaning&lt;/h2&gt;
&lt;p&gt;Taking the common tshirt-size priority list of &amp;quot;high&amp;quot;, &amp;quot;medium&amp;quot;, and &amp;quot;low&amp;quot;, it's quite common to want to tack on &amp;quot;highest&amp;quot; for a catastrophic situation and &amp;quot;lowest&amp;quot; for a backburner want-to-have. I understand the inclination, but there really is no meaning here.&lt;/p&gt;
&lt;p&gt;For &amp;quot;lowest&amp;quot; priority items, why even have it on your set of tasks? if this is something that your team &lt;em&gt;is&lt;/em&gt; going to do, then it should be understood in the context of the whole project and have a proper priority. If it's a wishful thought or a pipedream of one of the engineers (what this category is typically used for) then it's not a part of the project and it's wasting valuable space. Get rid of it.&lt;/p&gt;
&lt;p&gt;Similarly, if I really get an incident that requires immediate intervention, I'm not going to create a &amp;quot;highest&amp;quot; priority card for it. I'm getting everyone on a Zoom call, starting an incident document, and fixing the issue. &amp;quot;Highest&amp;quot; priority cards lag around because some individual needed to be placated that we're definitely considering this one as important. This is a misalignment, and it should be addressed as such. Everyone should have the same idea as to what we're marching towards.&lt;/p&gt;
&lt;h2&gt;Don't use &amp;quot;Medium&amp;quot;&lt;/h2&gt;
&lt;p&gt;No, really! Just like &amp;quot;highest&amp;quot; and &amp;quot;lowest&amp;quot;, this one ends up having no meaning. Using tshirt sizes, &amp;quot;medium&amp;quot; becomes the priority for 90% of all tasks - when someone creates a task they don't want to upset the boat by choosing &amp;quot;high&amp;quot;, but they definitely want it done so they're not going to select &amp;quot;low&amp;quot;. Instead, they use &amp;quot;medium&amp;quot; as the default &amp;quot;pffft, don't know don't care&amp;quot; option, tell the PM, then the PM doesn't reassign priority because they don't understand the full scope of why refactoring the &lt;code&gt;IDoSomething&lt;/code&gt; interface is really needed at this stage.&lt;/p&gt;
&lt;p&gt;A &amp;quot;medium&amp;quot; priority becomes a dumping ground for all tasks and leads to a situation where the &lt;em&gt;true&lt;/em&gt; priority never gets documented because everything is &amp;quot;medium&amp;quot;. This is a major source for misalignment acorss the entire team. Instead, my preferred set of priorities is &amp;quot;high&amp;quot;, &amp;quot;higher&amp;quot;, and &amp;quot;lower&amp;quot;. This forces us to consider whether it's more or less important, and recognizes that most tasks on the board are there because they have some heightened priority. For ideas or far-off things, I would suggest keeping a separate, non-prioritized list for documentation purposes.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/priority.png" alt="Assigning Priority" /&gt;&lt;/p&gt;
&lt;h1&gt;Beyond Priority&lt;/h1&gt;
&lt;p&gt;Priority is, for most of our projects, a simplified capture of many different complicated factors about our tasks - the perceived need of different groups of stakeholders, the hopes and wants of the development team, and the unkowns of new technologies or complicated use cases. In some projects however, things are more clear-cut. There might only be a single, contiguous group of users, or the focus might be so constrained as to be relatively obvious to all the parties involved.&lt;/p&gt;
&lt;p&gt;In these cases it could be that priority is actually too ambiguous, and measuring other factors might serve us better in terms of understanding task order and importance.&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;If the project as a whole is addressing a specific use case or need, it might be useful to measure tasks in terms of &lt;em&gt;customer impact&lt;/em&gt; and &lt;em&gt;business impact&lt;/em&gt;. You can use the modified tshirt sizes of &amp;quot;high&amp;quot;, &amp;quot;higher&amp;quot;, and &amp;quot;lower&amp;quot;, or you can just use &amp;quot;high&amp;quot; and &amp;quot;low&amp;quot; for each. By focusing on the different aspects of priority, this can greatly simplify the priority assigning process and make the documentation more meaningful.&lt;/p&gt;
&lt;p&gt;The types of impact can be varied depending on the project type as well - if the project is a heavy refactor then &lt;em&gt;team impact&lt;/em&gt; or &lt;em&gt;codebase impact&lt;/em&gt; might be good inclusions.&lt;/p&gt;
&lt;h2&gt;Necessity&lt;/h2&gt;
&lt;p&gt;When the project is narrowly defined but has several different and/or disparate groups of stakeholders, defining the priority of a task instead by its necessity for different kinds of stakeholders can be the best option. Again, the modified tshirt sizes or just &amp;quot;high&amp;quot; and &amp;quot;low&amp;quot; can be used here.&lt;/p&gt;
&lt;p&gt;The broader idea is to think about what kinds of divisions the definition of &lt;em&gt;your&lt;/em&gt; project has, and to consider breaking priority out along those lines. By making &amp;quot;priority&amp;quot; a more concrete concept, it will be easier to assign and reevaluate priority, and it will be more meaningful for everyone who needs to be aligned on it.&lt;/p&gt;
</description>
      <pubDate>Sun, 03 Mar 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-03-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">oops_i_noindexed_the_blog</guid>
      <link>https://ian.wold.guru/Posts/oops_i_noindexed_the_blog.html</link>
      <title>Oops, I noindexed the Blog!</title>
      <description>&lt;p&gt;Many months ago, I set up &lt;a href="https://umami.is/"&gt;Umami&lt;/a&gt; to track views on my blog. It doesn't track users, just counts page hits and referring sources and the like. The results were encouraging: I do get a fair amount of traffic. Hello! I realized too that about 50% of it is from mobile devices. There goes my theory that I don't need to make it look good on mobile...&lt;/p&gt;
&lt;p&gt;Last month I noticed a trend: I was getting many fewer clicks and almost no traffic was coming from Bing or Google (having a lot of .NET content I came up in Bing results a fair amount). This was odd but I attributed it to the holiday season. I was still getting plenty of traffic from Yandex and Baidu.&lt;/p&gt;
&lt;p&gt;Well, it happens that the decrease in traffic coincided with my release of &lt;a href="https://github.com/IanWold/StaticBlogroll"&gt;StaticBlogroll&lt;/a&gt;, a repo/template I made to encourage others to share a public blogroll. It's intended to be deployed with GitHub Pages, and I figured it would be good to include &lt;code&gt;noindex, nofollow&lt;/code&gt; by default in the &lt;code&gt;robots.txt&lt;/code&gt; for this site, lest others' articles might appear under my domain on Google. Unlikely, but I didn't want the chance.&lt;/p&gt;
&lt;p&gt;Well, as it happens, GitHub likes to deploy Pages sites under your domain if you've already registered it. Thus, my blogroll deployed to &lt;a href="https://ian.wold.guru/Blogroll"&gt;ian.wold.guru/Blogroll&lt;/a&gt;. No problem by my book!&lt;/p&gt;
&lt;p&gt;Well, as it also happens, Bing and Google will read &lt;em&gt;every&lt;/em&gt; &lt;code&gt;robots.txt&lt;/code&gt; file on your site. As it further happens, &lt;code&gt;noindex nofollow&lt;/code&gt; in &lt;em&gt;any&lt;/em&gt; &lt;code&gt;robots.txt&lt;/code&gt; overrides &lt;em&gt;everything&lt;/em&gt; else.&lt;/p&gt;
&lt;p&gt;Oops.&lt;/p&gt;
&lt;p&gt;Two learnings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;That's&lt;/em&gt; why I had fewer hits, and&lt;/li&gt;
&lt;li&gt;Yandex and Baidu are (maybe predictably) not following best practices.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Luckily, Bing and Google have webmaster tools with which you can notify any updates to these files, so the problem should be corrected. I've got no clue if some of my previously-most-popular articles will regain their positions though. I'm particularly happy with my article on &lt;a href="https://ian.wold.guru/Posts/end_to_end_encryption_witn_blazor_wasm.html"&gt;E2E encryption in Blazor WASM&lt;/a&gt; so I hope that one recovers. Finally, I've updated the default for StaticBlogroll and I've edited my (now only) &lt;code&gt;robots.txt&lt;/code&gt; for this site to exclude &lt;code&gt;/Blogroll&lt;/code&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 26 Jan 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-01-26T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">postgres_use_views_to_refactor_to_soft_delete</guid>
      <link>https://ian.wold.guru/Posts/postgres_use_views_to_refactor_to_soft_delete.html</link>
      <title>Postgres: Use Views to Refactor to Soft Delete</title>
      <description>&lt;p&gt;&lt;em&gt;Author's note: I have updated this article with some extra bits of information as well as a repository demonstrating this solution. I hope this is helpful!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the world of persistence, there's two main patterns (maybe &amp;quot;groups of patterns&amp;quot;) to handle deletion: &lt;em&gt;hard&lt;/em&gt; and &lt;em&gt;soft&lt;/em&gt; deletion. Hard deletion is the default for most database systems - when you &amp;quot;delete&amp;quot; a record it is wiped from the database; as soon as the delete is committed the data is gone forever. More common in most business scenarios - particularly server-side - is to retain the deleted data, just using a flag to hide the &amp;quot;deleted&amp;quot; data from the customer. This is soft deletion: it allows us to support more comprehensive internal reporting, maintain more complicated referential integrity, and we can support an &amp;quot;undo&amp;quot; button for our users.&lt;/p&gt;
&lt;p&gt;If you're designing a system from the ground up and you know you need to accommodate soft deletion, there's a whole host of implementations you can tailor to your needs. If you're updating an existing system from hard to soft delete though, you're more constrained. Yes, the data that has already been hard deleted is irrecoverable after your migration, but that's not your main concern. You might have a lot of tables and there's probably already a lot of code written to query against these tables. This refactor might seem like a lot of work at first glance.&lt;/p&gt;
&lt;p&gt;Luckily because &lt;a href="https://ian.wold.guru/Posts/just_use_postgresql.html"&gt;you're definitely using Postgres&lt;/a&gt;, this really isn't a major concern. There's a simple way using views and rules we can add soft deletion &lt;em&gt;without changing any of our queries&lt;/em&gt;! In fact, there are two options which you might want to chose in different situations. I've implemented these strategies on a few production systems with zero downtime, and I'm sure you'll be able to make just as quick work of it as I can.&lt;/p&gt;
&lt;p&gt;The first strategy keeps all records - those that are deleted and those that aren't - in a single table and uses a view to filter out deleted items. The second strategy adds a second table specifically for deleted items and uses a view to join the deleted and non-deleted records when needed. I have found the first option simpler to implement and maintain, while the second option is sometimes better in scenarios where I need to run a lot of different queries on the deleted vs. non-deleted data. These strategies could feasibly be combined in a single database, with some tables using one strategy and others the other, though consistency is probably better here.&lt;/p&gt;
&lt;p&gt;I've set up &lt;a href="https://github.com/IanWold/PostgresRefactorSoftDelete"&gt;a repository on GitHub&lt;/a&gt; which runs both of these solutions on a database. If you're seriously considering implementing a migration like this, I would encourage you to fork and play with my repo, I hope it can be an effective way to tinker with some of these ideas before moving into an actual codebase. Throughout the article I'll reference where each bit of code is in this repo.&lt;/p&gt;
&lt;h1&gt;Setting Up&lt;/h1&gt;
&lt;p&gt;I'm going to start by defining the existing system we're going to refactor. Let's consider a subset of an ecommerce system with tables &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;carts&lt;/code&gt;, and of course &lt;code&gt;cart_items&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE items (
    item_id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE carts (
    cart_id SERIAL PRIMARY KEY,
    user_id INT NOT NULL, -- I won't define this table
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE cart_items (
    cart_item_id SERIAL PRIMARY KEY,
    cart_id INT NOT NULL,
    item_id INT NOT NULL,
    quantity INT NOT NULL DEFAULT 1,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    CONSTRAINT fk_cart FOREIGN KEY (cart_id) REFERENCES carts (cart_id) ON DELETE CASCADE,
    CONSTRAINT fk_item FOREIGN KEY (item_id) REFERENCES items (item_id) ON DELETE CASCADE
);

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/Program.cs#L18 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we're going to need to support all the various queries currently written against these tables. In addition to all the queries &lt;code&gt;SELECT&lt;/code&gt;ing from these tables, we're inserting, updating, and indeed deleting from each of these tables - those queries all need to remain the same.&lt;/p&gt;
&lt;p&gt;There's a couple interesting queries I want to consider. Take the scenario where a cart is deleted:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;DELETE FROM carts WHERE cart_id = @cartId
-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/Program.cs#L139 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, it's pretty straightforward to assume that its items should delete at the same time, so we'll need to preserve the cascade functionality but with soft delete in mind. How about the following though:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;DELETE FROM items WHERE item_id = @itemId;
-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/Program.cs#L138 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our current production system, when we delete that item it will delete from the cart automatically. This is a good demonstration of why we care about soft delete - by preserving the data in our database we'll be able to tell the user that their dinner plate set is no longer available. Indeed, properly supporting that functionality is going to require some extra work in our code, but remember that &lt;em&gt;that&lt;/em&gt; is a feature add: we can do our soft delete refactor and deploy it, preserving the current functionality with minimal effort. Later iterations can build on this work to add the new capabilities in.&lt;/p&gt;
&lt;p&gt;This is the sort of planning work which needs to be done before beginning the refactor: The relationship between tables needs to be mapped out and understood. Typically you'll already have cascades set, so ideally this won't be difficult work to do.&lt;/p&gt;
&lt;h1&gt;Single Table&lt;/h1&gt;
&lt;p&gt;This is the first strategy I'll discuss. To implement this, we'll go through a few discrete steps. First, we'll add a &lt;code&gt;deleted&lt;/code&gt; flag to all of our tables, then we'll create a view for each table that excludes deleted records, and finally we'll update the definition of &lt;code&gt;DELETE&lt;/code&gt; to perform the soft delete. Those views will effectively replace our tables for all queries and commands. After the refactor, deleted items can be queried directly from the original tables, which for all other purposes will have been &amp;quot;hidden&amp;quot; by the new views.&lt;/p&gt;
&lt;h2&gt;Adding the Deleted Flag&lt;/h2&gt;
&lt;p&gt;The first step in our refactor will be to update every table we want to soft delete to add a new column. This will be the flag that determines whether the record is deleted or not. You can use just a boolean here, however this is an opportunity for us to capture more data and potentially make debugging a bit easier. I prefer using a nullable timestamp for my flag - &lt;code&gt;null&lt;/code&gt; signifies it is &lt;em&gt;not&lt;/em&gt; deleted, whereas the presence of a date indicates not only &lt;em&gt;that&lt;/em&gt; it was deleted, but also &lt;em&gt;when&lt;/em&gt;. Because soft deletion is commonly used to support internal reporting scenarios, this can sometimes be quite useful information.&lt;/p&gt;
&lt;p&gt;Adding the column is quite straightforward of course, it can be added with no effect on the overall system:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE items ADD COLUMN deleted TIMESTAMP DEFAULT NULL;

ALTER TABLE carts ADD COLUMN deleted TIMESTAMP DEFAULT NULL;

ALTER TABLE cart_items ADD COLUMN deleted TIMESTAMP DEFAULT NULL;

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SingleTableMigration.cs#L6 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be deployed at any time and will not affect the rest of the system unless you're doing something squirrely with your tables. Just note though that if there's a long gap between your migration steps, new tables should have this column included. Because this column isn't used, it will be null for all records, and until we proceed to the next steps records will still be hard deleted.&lt;/p&gt;
&lt;h2&gt;Hiding Deleted Data&lt;/h2&gt;
&lt;p&gt;This second step can also be deployed at any time after the first step with no change in the functionality of the system: we're going to hide any data where &lt;code&gt;deleted&lt;/code&gt; is not null. In order to accomplish this without needing to rewrite any of our queries, constraints, or the like, we'll use views to achieve this.&lt;/p&gt;
&lt;p&gt;For the unfamiliar, views act just like tables, but are essentially projections of other underlying tables and views. You define them with a &lt;code&gt;SELECT&lt;/code&gt; statement just as you would query any other data. This allows you to marry commonly-referenced data together, but in our case we're going to use them a bit differently.&lt;/p&gt;
&lt;p&gt;For &lt;em&gt;each&lt;/em&gt; table which has a &lt;code&gt;deleted&lt;/code&gt; column, we're going to define a new view which excludes the deleted values of the underlying column. Now, because we have how many references to the tables named &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;carts&lt;/code&gt;, and &lt;code&gt;cart_items&lt;/code&gt;, and it's &lt;em&gt;these&lt;/em&gt; references from which we want to exclude the deleted items, we're going to give our views the names of the current tables and rename the current tables.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE items RENAME TO items_all;
CREATE VIEW items AS SELECT * FROM items_all WHERE deleted IS NULL;

ALTER TABLE carts RENAME TO carts_all;
CREATE VIEW carts AS SELECT * FROM carts_all WHERE deleted IS NULL;

ALTER TABLE cart_items RENAME TO cart_items_all;
CREATE VIEW cart_items AS SELECT * FROM cart_items_all WHERE deleted IS NULL;

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SingleTableMigration.cs#L12 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I use the convention that the table name is suffixed &amp;quot;_all&amp;quot;, signifying of course that the tables contain &amp;quot;all&amp;quot; of the records. Your requirements might be different, so pick a naming convention that makes sense in the context. I recommend &lt;a href="https://ian.wold.guru/Posts/its_better_to_be_consistently_incorrect_than_consistently_correct.html"&gt;being extremely consistent&lt;/a&gt; here, and I also recommend picking a convention that is obvious in distinguishing the tables.&lt;/p&gt;
&lt;p&gt;After deploying this update, you'll find that your system is still working exactly as it has in the past. Postgres passes any commands on these views down to the underlying table, so when you &lt;code&gt;INSERT&lt;/code&gt; into one of these views, the data will be inserted into its respective table. Neat!&lt;/p&gt;
&lt;h2&gt;Make Deletes Not Delete&lt;/h2&gt;
&lt;p&gt;This is the crucial step now: we need to redefine what a &amp;quot;delete&amp;quot; means. While the last two steps alter neither the behavior of the system nor the representation, this step will alter the representation of the data, and so long as we're dilligent it will not alter the behavior for your users. Because you're altering the data representation now though, be careful!&lt;/p&gt;
&lt;p&gt;There's two steps we need to take here at once. First, we need to tell Postgres that when it gets a &lt;code&gt;DELETE&lt;/code&gt; command for one of these tables that it actually needs to &lt;em&gt;update&lt;/em&gt; the &lt;code&gt;deleted&lt;/code&gt; column instead. Second, we need to define the new cascade behaviors to propogate the soft deletion in the intended way.&lt;/p&gt;
&lt;p&gt;To redefine &lt;code&gt;DELETE&lt;/code&gt;, Potgres allows us to create a &lt;em&gt;rule&lt;/em&gt; to perform an alternate action &lt;em&gt;instead&lt;/em&gt;. Typical for Postgres, this syntax is quite straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE RULE rule_soft_delete AS ON DELETE TO items DO INSTEAD (UPDATE items_all SET deleted = CURRENT_TIMESTAMP WHERE item_id = OLD.item_id);
CREATE RULE rule_soft_delete AS ON DELETE TO carts DO INSTEAD (UPDATE carts_all SET deleted = CURRENT_TIMESTAMP WHERE cart_id = OLD.cart_id);
CREATE RULE rule_soft_delete AS ON DELETE TO cart_items DO INSTEAD (UPDATE cart_items_all SET deleted = CURRENT_TIMESTAMP WHERE cart_item_id = OLD.cart_item_id);

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SingleTableMigration.cs#L23 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we need to handle the cascades. Let's start with the simple case where we know that deleting a cart should cascade to each of its items. The rule we define will act on updates to the &lt;code&gt;carts_all&lt;/code&gt; table and propogate new, non-null values for &lt;code&gt;deleted&lt;/code&gt; to the cart items:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE RULE rule_cascade_deleted_cart_items AS ON UPDATE TO carts_all
    WHERE OLD.deleted IS DISTINCT FROM NEW.deleted
    DO ALSO UPDATE cart_items_all SET deleted = NEW.deleted WHERE cart_id = OLD.cart_id;

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SingleTableMigration.cs#L27 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recall that the other cascade case, where we delete an item, is more complicated. The current behavior (which we are just trying to preserve for now) is that items are deleted from a cart when the items themselves are deleted. The state we want to achieve (some time after this migration) is that items will still show in the cart but as &amp;quot;no longer available&amp;quot;. That will take some extra coding to implement, however depending on the current state of our system we might be able to achieve some cost savings.&lt;/p&gt;
&lt;p&gt;If you just want to keep the refactor simple or your system wouldn't currently support an alternate scheme, then you can implement the exact same rule but for items, then remove it later once you're ready. However, consider whether your current system would be able to do without the cascade right off the bat here. Suppose you have some logic on your cart page which attempts to get item data for all items in the cart and will not display items for which it cannot find data. In this case, you do not need to add the cascade, and the users will still observe the same behavior they currently have!&lt;/p&gt;
&lt;p&gt;If you do decide not to carry over the cascade, there is the matter of the foreign key reference - if we soft delete a record from the &lt;code&gt;items_all&lt;/code&gt; table, the foreign key on &lt;code&gt;cart_items.item_id&lt;/code&gt; is still referencing the new &lt;code&gt;items&lt;/code&gt; view and will break! In this case, we'll need to update the foreign key now:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-SQL"&gt;ALTER TABLE cart_items_all DROP CONSTRAINT fk_item;
ALTER TABLE cart_items_all ADD CONSTRAINT fk_items_all FOREIGN KEY (item_id) REFERENCES items_all (item_id) ON DELETE CASCADE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, this step does &lt;em&gt;not&lt;/em&gt; need to be done now if you are adding the manual cascade for &lt;code&gt;items&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Accessing Deleted Records&lt;/h2&gt;
&lt;p&gt;At this point, your migration is finished. Congratulations! In order to get records that have been deleted, you just need to query the &lt;code&gt;_all&lt;/code&gt; table for that record:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM items_all WHERE ...
-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SingleTableMigration.cs#L37 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to hard delete any records, you can &lt;code&gt;DELETE&lt;/code&gt; from the &lt;code&gt;_all&lt;/code&gt; table:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;DELETE FROM items_all WHERE ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Separate Deleted Table&lt;/h1&gt;
&lt;p&gt;This is the second strategy, where deleted items are kept in separate tables. We'll also be able to work through some discrete steps to implement this. First, for each table we'll create a corresponding deleted table, and then we'll also create a corresponding view to unite the two. Finally, we'll alter the definition of &lt;code&gt;DELETE&lt;/code&gt; to perform the soft delete. After the refactor, we'll be able to consult the separate deleted tables to work with our deleted records.&lt;/p&gt;
&lt;h2&gt;Adding the Deleted Tables&lt;/h2&gt;
&lt;p&gt;The first step in our refactor will be to add a corresponding table for each existing table that will include the deleted fields. Instead of adding a &lt;code&gt;deleted&lt;/code&gt; timestamp on the main table itself, it's sufficient for only this copy table to contain the field. We'll use &lt;code&gt;LIKE&lt;/code&gt; to copy the schema of the base tables.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE items_deleted (
    deleted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    LIKE items INCLUDING ALL
);

CREATE TABLE carts_deleted (
    deleted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    LIKE carts INCLUDING ALL
);

CREATE TABLE cart_items_deleted (
    deleted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    LIKE cart_items INCLUDING ALL
);

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SeparateTableMigration.cs#L6 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My same note from earlier about naming conventions applies, though I think &lt;code&gt;_deleted&lt;/code&gt; is probably the suffix 99% of us will ever use for these.&lt;/p&gt;
&lt;p&gt;There is one issue with this snippet though - can you spot it? The problem is in &lt;code&gt;cart_items_deleted&lt;/code&gt;: &lt;code&gt;LIKE ... INCLUDING ALL&lt;/code&gt; will copy over the &lt;em&gt;entire&lt;/em&gt; table schema, foreign key references included. In order to get around this, we'll need to not use &lt;code&gt;INCLUDING ALL&lt;/code&gt; and manually copy over the foreign key constraints:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE cart_items_deleted (
    deleted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    LIKE cart_items,
    
    CONSTRAINT fk_cart FOREIGN KEY (cart_id) REFERENCES carts_deleted (cart_id) ON DELETE CASCADE,
    CONSTRAINT fk_item FOREIGN KEY (item_id) REFERENCES items_deleted (item_id) ON DELETE CASCADE
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But wait, there's more! This works all well and good so long as the carts and items you're referencing from the &lt;code&gt;cart_items_deleted&lt;/code&gt; table actually exist in &lt;code&gt;items_deleted&lt;/code&gt; and &lt;code&gt;carts_deleted&lt;/code&gt;. This is sure to not be the case, as it makes perfect sense that I might delete a cart item without that item having been deleted! Supposing that we &lt;em&gt;did&lt;/em&gt; have a schema where we would expect the referant to be deleted also though, are we sure that &lt;em&gt;that&lt;/em&gt; record is being deleted first?&lt;/p&gt;
&lt;p&gt;This can all get quite tricky. If you don't care about the referential integrity of your soft-delted data (but trust me, you probably &lt;em&gt;should&lt;/em&gt;), then you can just forego the foreign key constraints. If you do care about the references though, then you'll need to spend some time thinking about the best approach here. A whole article can be written on potential approaches so I won't distract us here, but beware of this trap!&lt;/p&gt;
&lt;p&gt;This demonstrates the extra complexity of this second approach. It allows for a greater separation between deleted and non-deleted items, though it's important to consider which functionality and level of complexity your system requires.&lt;/p&gt;
&lt;h2&gt;Adding the Combined Views&lt;/h2&gt;
&lt;p&gt;There are inevitably operations we're going to want to perform on the whole dataset for each table - deleted &lt;em&gt;and&lt;/em&gt; non-deleted items all as one. To facilitate this, we'll create a view for each table/deleted table pair which unions the two:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE VIEW items_combined AS SELECT null AS deleted, * FROM items UNION ALL SELECT * FROM items_deleted;
CREATE VIEW carts_combined AS SELECT null AS deleted, * FROM carts UNION ALL SELECT * FROM carts_deleted;
CREATE VIEW cart_items_combined AS SELECT null AS deleted, * FROM cart_items UNION ALL SELECT * FROM cart_items_deleted;

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SeparateTableMigration.cs#L23 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At least that's easy!&lt;/p&gt;
&lt;h2&gt;Make Delete Not Delete&lt;/h2&gt;
&lt;p&gt;In order to make &lt;code&gt;DELETE&lt;/code&gt;s perform a soft delete in this setup, we need to do a couple things when deleting: first, we need to copy the record into the respective &lt;code&gt;_deleted&lt;/code&gt; table, then we need to allow the delete to actually happen.&lt;/p&gt;
&lt;p&gt;Here we have a couple options. We could use a &lt;code&gt;RULE&lt;/code&gt;...&lt;code&gt;DO ALSO&lt;/code&gt; to perform the insert after the delete:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE RULE soft_delete AS ON DELETE TO items DO ALSO (
    INSERT INTO items_deleted (item_id, name, price, created)
    VALUES (OLD.item_id, OLD.name, OLD.price, OLD.created)
);
CREATE RULE soft_delete AS ON DELETE TO carts DO ALSO (
    INSERT INTO carts_deleted (cart_id, user_id, created)
    VALUES (OLD.cart_id, OLD.user_id, OLD.created)
);
CREATE RULE soft_delete AS ON DELETE TO cart_items DO ALSO (
    INSERT INTO cart_items_deleted (cart_item_id, cart_id, item_id, quantity, created)
    VALUES (OLD.cart_item_id, OLD.cart_id, OLD.item_id, OLD.quantity, OLD.created)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, because we're combining operations I would tend towards preferring a trigger in this specific scenario:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE OR REPLACE FUNCTION items_soft_delete() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO items_deleted (item_id, name, price, created)
    VALUES (OLD.item_id, OLD.name, OLD.price, OLD.created);
    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_items_delete BEFORE DELETE ON items FOR EACH ROW EXECUTE FUNCTION items_soft_delete();

CREATE OR REPLACE FUNCTION carts_soft_delete() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO carts_deleted (cart_id, user_id, created)
    VALUES (OLD.cart_id, OLD.user_id, OLD.created);
    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_carts_delete BEFORE DELETE ON carts FOR EACH ROW EXECUTE FUNCTION carts_soft_delete();

CREATE OR REPLACE FUNCTION cart_items_soft_delete() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO cart_items_deleted (cart_item_id, cart_id, item_id, quantity, created)
    VALUES (OLD.cart_item_id, OLD.cart_id, OLD.item_id, OLD.quantity, OLD.created);
    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_cart_items_delete BEFORE DELETE ON items FOR EACH ROW EXECUTE FUNCTION cart_items_soft_delete();

-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SeparateTableMigration.cs#L29 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You then have the same considerations regarding cascades and foreign key references as you had in the last refactor option. I'll leave that as an exercise for you to extend the trigger functions to include that behavior.&lt;/p&gt;
&lt;h2&gt;Accessing Deleted Records&lt;/h2&gt;
&lt;p&gt;This is either the benefit or the drawback of this setup compared to the first scheme. In order to access deleted records, you query the &lt;code&gt;_deleted&lt;/code&gt; table:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM items_deleted WHERE ...
-- https://github.com/IanWold/PostgresRefactorSoftDelete/blob/main/SeparateTableMigration.cs#L58 [tl! autolink]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And of course all records together from the separate view:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM items_combined WHERE ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your application will need to regard these three sets differently in distinct queries, then this setup is advantageous: there's less chance of writing the queries incorrectly and causing bugs. However, if you do need to consult the blended data or perform mostly the same queries, the first option might be better.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Implementing soft delete on an existing system can seem like an exceptionally daunting task if you consider it from the perspective of needing to rewrite whole swaths of existing queries to suppot it. Not to mention, the incidence rate of bugs from such a hands-on refactor will be quite high!&lt;/p&gt;
&lt;p&gt;The less we can touch our system during a refactor, the better. As with any refactor, this one requires some careful planning up front to understand the relationships between tables as data is deleted. Once that's understood, the refactor can proceed really quite fast &lt;em&gt;just&lt;/em&gt; by changing the database schema, preserving the existing functionality and, crucially, the existing queries &lt;em&gt;and&lt;/em&gt; the underlying semantics of those queries.&lt;/p&gt;
&lt;p&gt;This doesn't absolve you of the careful work in ensuring your refactor works correctly (doubly so as you're tinkering around in the persistence). However, it does put more guardrails in place. Being able to move quickly &lt;em&gt;and&lt;/em&gt; deliberately, touching as little as possible, leaves you in the best position for success in your refactor.&lt;/p&gt;
</description>
      <pubDate>Sat, 05 Oct 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-10-05T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">principles_for_successful_teams</guid>
      <link>https://ian.wold.guru/Posts/principles_for_successful_teams.html</link>
      <title>Principles for Successful Teams</title>
      <description>&lt;p&gt;There's a heck of a lot of blabbering that's been written on the topic of what makes a team, department, or firm successful. It's (almost) entirely crap, in my experience. The bulk of it seems to be an exercise in justifying the unjustifiable jobs that middle managers and consultants take up; this is a relatively wealthy bunch so I suppose it makes some sense to sell snake oil to them.&lt;/p&gt;
&lt;p&gt;No doubt we've all had experiences with incoherent decisions from higher-ups, illogical constraints on work, or even micromanaging. We look at each other and wonder &amp;quot;what if we &lt;em&gt;just had the space&lt;/em&gt; to get our work done.&amp;quot; Indeed it seems more often than not that altogether engineers, QAs, and the like are able to make better decisions collectively than in the typical top-down structure.&lt;/p&gt;
&lt;p&gt;On realizing that, the principles that underlie the success are quite clear: we're all professionals who can A. deliver their work and B. collaboratively agree on decision points. In that spirit, here are (I think) the only four principles that are really needed between us and our colleagues in any team, department, or firm.&lt;/p&gt;
&lt;h2&gt;1. Each Colleague is Independent&lt;/h2&gt;
&lt;p&gt;Everyone must have ownership over their own efforts and must be able todeliver their work without micromanagement or unnecessary restrictions. Where each colleague is a professional capable of particular tasks, they must be unburdened in fulfilling them.&lt;/p&gt;
&lt;p&gt;The diversity of thought and approaches to work strengthens the whole effort; each colleague must be able to communicate their ideas, opinions, and feedback.&lt;/p&gt;
&lt;h2&gt;2. All Colleagues Form the Whole&lt;/h2&gt;
&lt;p&gt;Our teams, departments, and firms are formed, entirely, by the sum of the contributions by colleagues. The whole cannot be formed without the contributions from everyone; each colleague is necessary irrespective of title or experience.&lt;/p&gt;
&lt;p&gt;Effort must be distributed across all colleagues, and no individual should find themselves a &amp;quot;linchpin&amp;quot; in holding others back.&lt;/p&gt;
&lt;p&gt;Each colleague must have access to the same resources, and all colleagues should have the same information available to them. Everyone should be &amp;quot;on the same page.&amp;quot;&lt;/p&gt;
&lt;h2&gt;3. Each Colleague Supports Each Other&lt;/h2&gt;
&lt;p&gt;Collaboration is the only way to deliver efforts involving more than one colleague; competition (however friendly) and rugged individualism (however ideal) only prohibit progress on work.&lt;/p&gt;
&lt;p&gt;Each colleague must have camraderie with each other; each must share their knowledge, time, and ability with each other.&lt;/p&gt;
&lt;h2&gt;4. All Colleagues Compromise&lt;/h2&gt;
&lt;p&gt;There can't be a proper balance between individual and team effort without compromise, and compromise is required to maintain the other principles.&lt;/p&gt;
&lt;p&gt;Each colleague must have a voice in decision-making, feedback, and leadership over their efforts; all colleagues in turn must bring these together into a productive decision.&lt;/p&gt;
&lt;p&gt;Each colleague must participate in the activities of the whole team, department, or firm.&lt;/p&gt;
</description>
      <pubDate>Mon, 03 Mar 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-03-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">provocative_holub_opinion_tier_list</guid>
      <link>https://ian.wold.guru/Posts/provocative_holub_opinion_tier_list.html</link>
      <title>Provocative Holub Opinion Tier List</title>
      <description>&lt;p&gt;If you're new to my blog, one important thing to note is that my thinking regarding team and organizational practices tends to align with that of &lt;a href="https://holub.com/"&gt;Allen Holub&lt;/a&gt;, at least so far as what I've gleaned from a number of talks and blog posts he's done.&lt;/p&gt;
&lt;p&gt;Holub has come upon a number of beliefs which, while not necessarily being among the most controversial opinions in our industry, are nonetheless heterodox to the normal prescriptions we find in our industry with respect to organization. He has branded a number of these in a hashtag format such as &amp;quot;#NoStandups&amp;quot; or &amp;quot;#NoRetrospectives,&amp;quot; stemming from an earlier trend around the hashtag &amp;quot;#NoEstimates.&amp;quot; Below I sort these into &lt;a href="https://en.wikipedia.org/wiki/Tier_list"&gt;tiers&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;#NoEstimates&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://holub.com/noestimates-an-introduction/"&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is the original &amp;quot;no&amp;quot; hashtag, there's a reason it began a trend. This is the one best-supported by empirical data: studies have shown that counting cards (rate of backlog accrual vs rate of completion) allow projecting the completion date of a project within plus or minus two weeks. By using completion date projection instead of the more common and opposite practice of trying to estimate completion dates, businesses and teams gain a finer level of control on what products they ship when.&lt;/p&gt;
&lt;p&gt;This one goes in the S tier: it is very widely applicable and impossible to argue against when outside of limited cases.&lt;/p&gt;
&lt;h2&gt;#NoStandups&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://holub.com/nostandups/"&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In his opening paragraph on the matter, Holub introduces the standup meeting as &amp;quot;a band-aid that’s hiding ineffective communication.&amp;quot; This may be true in a number of cases, and regular, daily standup meetings may well not be needed by a lot of teams (particularly long-running product teams). However, There are lots of cases where a daily check-in meeting is a benefit, and the standup format is a good standard for check-in meetings.&lt;/p&gt;
&lt;p&gt;This one goes in the B tier: most teams will certainly benefit from reevaluating how they check in and monitor progress and I suspect that most honest teams will conclude that a daily standup isn't necessary, however &amp;quot;#NoStandups&amp;quot; isn't as widely applicable a banner.&lt;/p&gt;
&lt;h2&gt;#NoRetrospectives&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://holub.com/improvement-boards/"&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This idea is to track process impediments as cards in the same way we track work items as cards; that every sprint (or whatever iteration) at least one impediment will be worked on and resolved. This is a good idea! Is that what you thought of when you read &amp;quot;#NoRetrospectives&amp;quot; though?&lt;/p&gt;
&lt;p&gt;This one goes in D tier: apart from being false advertising - it is &lt;em&gt;not&lt;/em&gt; advocating &amp;quot;no retrospectives&amp;quot; - it would also probably function better in the form of &amp;quot;here's how to give your retro meetings some teeth.&amp;quot;&lt;/p&gt;
&lt;h2&gt;#NoAccountability&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://holub.com/noaccountability/"&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Holub argues, convincingly I think, that &amp;quot;accountability&amp;quot; in the business sphere tends to be reduced to meaning &amp;quot;you can be punished for this thing that I, your manager, am foisting on you.&amp;quot; The #NoAccountability idea then is to promote shared ownership and collaboration over point-person ownership.&lt;/p&gt;
&lt;p&gt;This one goes in A tier: while not as flashy as the more widely cited #NoEstimates, it identifies a real, universal problem and posits a workable philosophy as the solution. Unfortunately, the nature of the solution is not a step-by-step &amp;quot;here's what to do,&amp;quot; instead requiring a mindset change to effect.&lt;/p&gt;
&lt;h2&gt;See Also&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://holub.com/words/"&gt;Words&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Sun, 15 Feb 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-02-15T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">publish_your_blogroll_now</guid>
      <link>https://ian.wold.guru/Posts/publish_your_blogroll_now.html</link>
      <title>Publish Your Blogroll Now</title>
      <description>&lt;p&gt;Do you read blogs? Of course you do! How do you keep those blogs saved? How do you keep up with their latest posts? How do you share these blogs with everyone else?&lt;/p&gt;
&lt;p&gt;I recently became motivated (by the realization that &lt;a href="https://ian.wold.guru/Posts/i_have_a_blogroll_now.html"&gt;I keep forgetting about my RSS reader&lt;/a&gt;) to answer these questions by creating a blogroll. But not just any blogroll, no! A &lt;em&gt;super cool&lt;/em&gt; blogroll - a standalone, statically generated page that lists all the latest posts from the blogs I follow. It's like an RSS reader but way better: I can share it with the internet, and I set my browser's homepage to the collection of latest posts. I can keep up with my reading and share it with everyone.&lt;/p&gt;
&lt;p&gt;The best part is you can too! I developed &lt;a href="https://github.com/IanWold/StaticBlogroll"&gt;StaticBlogroll&lt;/a&gt;, a very creatively-named template repo you can use to get your own blogroll page going in no time flat. It uses GitHub Actions and Pages, so there's no work beyond just a little config to set up.&lt;/p&gt;
&lt;h1&gt;Okay, but why do I need a blogroll?&lt;/h1&gt;
&lt;p&gt;I'm a big advocate that everyone should be maintaining a blogroll. Not super actively - maybe more passively - but you should definitely have one. No doubt there's dozens of blogs a year you tune into - an article here to get help on an issue, an article there to learn about a topic, articles for fun or insight or curiousity.&lt;/p&gt;
&lt;p&gt;A public blogroll helps others discover the blogs you enjoy. Even if only a few friends end up looking at your blogroll, you're helping the author find new readers at the same time you're helping your friends find something new. Even just recognizing an author on your blogroll is a kind gesture that shows your appreciation for their work.&lt;/p&gt;
&lt;p&gt;If you happen to have your own blog, keeping a blogroll is an even better investment. Connecting your readers with blogs of similar interests gives a better, deeper experience for your readers. As for me, I feel like I can represent more aspects of my interests and personality by linking to other blogs that are better-focused than my blog on topics which I enjoy and influence my writing here.&lt;/p&gt;
&lt;p&gt;So what's the utility in a &lt;em&gt;statically generated&lt;/em&gt; blogroll? The only thing I've added to take advantage of this setup (yet) is to read the latest posts in from the RSS feeds on my blogroll. As I mentioned, this was selfish because I needed an easy way to read them. I'm certain that could be handy for you as well, but I also like the idea that this makes it easier for visitors to my blogroll to skim through for articles they might be interested in.&lt;/p&gt;
&lt;h1&gt;That sounds awesome! How do I get one?&lt;/h1&gt;
&lt;p&gt;I've published &lt;a href="https://github.com/IanWold/StaticBlogroll"&gt;StaticBlogroll&lt;/a&gt; as a template repository on GitHub; you can &lt;a href="https://github.com/new?template_name=StaticBlogroll&amp;amp;template_owner=IanWold"&gt;create a new repo&lt;/a&gt; from that template and &lt;a href="https://github.com/IanWold/StaticBlogroll?tab=readme-ov-file#setup"&gt;follow the setup steps&lt;/a&gt; on the readme - these should take no more than 5 minutes to get all set up!&lt;/p&gt;
&lt;p&gt;The way it works is very simple: there's a &lt;code&gt;config.json&lt;/code&gt; at the root level that has all the configurations. This is the name of the page, some headers and the like, but most importantly the list of &lt;code&gt;feeds&lt;/code&gt;. These are the URLs to the RSS feeds of the blogs you want on your roll.&lt;/p&gt;
&lt;p&gt;From there, there is a GitHub Actions workflow that generates the static site based on this config, though there's a few other files (templates and static files) you can peruse at the root level if you want to customize it further. The workflow runs whenever you push to &lt;code&gt;main&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; it runs once a day at midnight to regenerate the latest posts.&lt;/p&gt;
&lt;p&gt;After you've gone through the simple steps to configure Actions and Pages, you'll have a blogroll page of your own! Woohoo! The only next step would be to share it with the world. I set up a discussion topic on my repository to showcase blogrolls made with it - please &lt;a href="https://github.com/IanWold/StaticBlogroll/discussions/new?category=showcase"&gt;add your own&lt;/a&gt; so I can see the cool blogs y'all read.&lt;/p&gt;
&lt;p&gt;If you'd like a reference with some further customizations, check out &lt;a href="https://github.com/IanWold/Blogroll"&gt;my blogroll repo&lt;/a&gt; made off of this template.&lt;/p&gt;
</description>
      <pubDate>Sun, 03 Nov 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-11-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">pull_requests_are_just_fine_thanks</guid>
      <link>https://ian.wold.guru/Posts/pull_requests_are_just_fine_thanks.html</link>
      <title>Pull Requests are Just Fine, Thanks</title>
      <description>&lt;p&gt;There's a degree of popularity around claiming that pull requests are bad and we shouldn't do them. I'm not sure that this is the majority opinion, but it's one that over the years has been popularized by some notable voices like &lt;a href="https://www.youtube.com/watch?v=ASOSEiJCyEM"&gt;Dave Farley&lt;/a&gt;, eched by a host of easy-to-find blog posts about the &lt;a href="https://blog.arkency.com/disadvantages-of-pull-requests/"&gt;their disadvantages&lt;/a&gt; or &lt;a href="https://thinkinglabs.io/articles/2024/02/22/the-good-and-the-dysfunctional-of-pull-requests.html"&gt;their dysfunctionality&lt;/a&gt; or even &lt;a href="https://thinkinglabs.io/articles/2024/02/22/the-good-and-the-dysfunctional-of-pull-requests.html"&gt;how they are hated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A review of these articles (and plenty of others which crop up from time to time) reveal a common list of gripes with PRs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PRs give too much power to reviewers; open to exploitation&lt;/li&gt;
&lt;li&gt;If branches live long there are merge conflicts&lt;/li&gt;
&lt;li&gt;Large PRs are difficult to review&lt;/li&gt;
&lt;li&gt;If the team only gives superficial reviews there's no point&lt;/li&gt;
&lt;li&gt;PRs reduce or remove two-way communication (async over sync communication)&lt;/li&gt;
&lt;li&gt;A review at end of dev cycle is too late&lt;/li&gt;
&lt;li&gt;PR lag increases the amount of work in progress&lt;/li&gt;
&lt;li&gt;PRs make the development feedback loop longer; shorter feedback loops are better&lt;/li&gt;
&lt;li&gt;Sometimes it's easier to fix a bug than to explain the fix&lt;/li&gt;
&lt;li&gt;PRs discourage continuous refactoring&lt;/li&gt;
&lt;li&gt;PRs lead to negative emotions; reviewers might not be emotionally tactful&lt;/li&gt;
&lt;li&gt;Adopting PRs for the sake of it makes purpose unclear&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These complaints fall into a couple of buckets: there's problems with power dynamics, poor practices on part of reviewers, and poor practices on the part of authors. Then what to do instead of a PR? Farley would have you do pair programming before merging your code into main, while others would do away with the mandatory code review altogether. These are the practices we used before the PR came around - like many of these commentators, I did not use manual PRs for the first years I was a software engineer. In fact, I think it was almost a decade before I worked on a team which adopted the manual review.&lt;/p&gt;
&lt;p&gt;One thing I try to espouse on this blog is a way of thinking conditionally about different options we encounter as software engineers - whether that's process, architectural, or whatever decisions. In this instance, I don't want to say &amp;quot;always use PRs and never use push-directly-to-main,&amp;quot; instead I'll want to give reasonable and meaningful conditions as to when one is preferred. I don't get the same sense from most of these anti-PR articles. Sure, they do pay some service to cases where PRs are preferred, but this is superficial and unserious. The suggestions that PRs are &lt;em&gt;only&lt;/em&gt; ever going to be useful in open source projects or when all members of a team seriously distrust each other are ridiculous and unhelpful.&lt;/p&gt;
&lt;p&gt;When will I suggest you use PRs? Whenever you want. When to use some alternative type of code review? Whenever your team wants. When to not review at all? Whenever you want. I've developed with teams adopting all manner of schemes and at the end of the day the software all ends up getting written. Usually. The only caveat, as with all process things, is to make sure you're measuring it and able to identify if a process is holding you back. Don't be afraid to experiment with something different!&lt;/p&gt;
&lt;p&gt;Our industry seems to have adopted (from my perspective, at least) mandatory PRs as the default way of doing code review. I'm perfectly fine with that; it's unfortunate when an organization might prohibit teams from experimenting otherwise, but that's not a hill I want to die on. Personally, I like PRs as a default, and I think the the anti-PR claims I enumerated above are well-overblown. In fact, I like PRs a lot, and I want to respond to those supposed disadvantages and argue in favor of PRs. I'll start with the last bullet I listed: we probably shouldn't be adopting processes which we're unable to justify, so if your team only adopts PRs &amp;quot;just because&amp;quot; then by all means have a rethink on that one.&lt;/p&gt;
&lt;h1&gt;Why PRs Aren't That Bad&lt;/h1&gt;
&lt;p&gt;There are a couple points made on the topic of power, that is, ways that PRs are perceived to give too much power to reviewers. When a comment is blocking a PR that shouldn't be blocking one is an example of such a power dynamic, but the more subtle one is when a very influential member of a team leaves a suggestion which - &lt;em&gt;wink wink nudge nudge&lt;/em&gt; - isn't really just a &amp;quot;suggestion&amp;quot;. These are concerns with team dynamics though. I know engineers tend to be a less social segment of society, but has the author of the PR with the blocking comment tried talking to the blocking reviewer? If PRs were not used would the very-insistent suggester still find other avenues for their so-called suggestions? Any process - PRs included - adopted by a team with unhealthy power dynamics will be brought down by those dynamics. PRs aren't more or less susceptible to this.&lt;/p&gt;
&lt;p&gt;Two other problems deal with PR size: large PRs are difficult to meaningfully review and long-lived branches tend to have more merge conflicts. Indeed these problems can come up, and if this is a sticking problem for your team it can be time to experiment with something else. In order to prevent this problem, branches should be &lt;em&gt;very&lt;/em&gt; small and &lt;em&gt;very&lt;/em&gt; short-lived. Indeed, plenty of features take a lot of code to implement, so the strategy is to PR frequently with the smallest possible changes at any point. Adopting a culture in the team to integrate changes at a fine-grained level like this is important not &lt;em&gt;only&lt;/em&gt; if you're using PRs. In fact, any process will benefit from frequent integration. In fact, if you happen to be on a team where each engineer prefers to work on large, discrete features in isolation with infrequent integration, no process will help you here.&lt;/p&gt;
&lt;p&gt;If you're frequently engaging reviews then - as you should be doing whether or not you're using a PR process - you're solving two other issues brought up with PRs: the feedback loop is going to be much shorter and the reviews aren't only going to be coming in at the end of the dev cycle. Indeed, opening small PRs that are easily-reviewable thoughout the dev cycle ends up being a good way to engage colleagues on feedback. The other half of this is reviewer etiquitte - our teammembers need to be diligent about providing timely, meaningful reviews. If I'm only ever asking them to review a few files though, I find that this ask isn't just easy but indeed one to which my colleagues are eager to respond.&lt;/p&gt;
&lt;p&gt;Continuing to adopt a healthy team culture will solve more of our problems with PRs and, since the issues with team culture aren't actually about the PR process, will help to solve the same problems in any non-PR processes. With frequent, small PRs that are easily and eagerly reviewed, there is not a PR lag which results in more work being in progress. Indeed, as colleagues I find that we do want to give good feedback, so having removed the burden of giving feedback more often than not I find that I get that feedback. It certainly can be a problem with PR processes that they lead to superficial reviews, but any process with reviews is susceptible to this in different ways. Whatever your process, identify how it might discourage meaningful reviews and address that. Oh look, we've crossed off another bullet!&lt;/p&gt;
&lt;p&gt;On that point, I'm also not sure that a PR process necessarily leads to more hurt feelings than any other process. Any negative emotions I get from PRs are, in my experience, the same as what come up from other points during development. They're mitigated by communicating with colleagues; keep in mind that the PR comments section isn't the only communications channel available to us! So i'm also curious why it's felt that PRs discourage two-way communication: if you're not communicating enough with colleagues then you should ... communicate with them? If they rebuff you this is a larger contention for a broader group. Certainly if you identify a PR process as necessarily causing this in the team, by all means try something else. I just mean to be skeptical that the PR process is really the root cause of this?&lt;/p&gt;
&lt;p&gt;I've left two negative points about PRs that I think are actually good points. First, I strongly concur that it is sometimes easier to fix a bug than to explain it. Does your team care about documenting fixes? If not, and if the team is comfortable with members pushing fixes with some other process, then I can certainly see how a PR process is an impediment. I don't think most teams are like this though; personally I very much value documenting bugs and their fixes, and I think that bugfixes are the best problems to get a second set of eyes on. In this case, the explanation issue isn't necessarily a problem with a PR process seeing as you're going to need to provide the documentation to someone &lt;em&gt;anyway&lt;/em&gt;. It's admittedly difficult for me to imagine a scenario where I want to push a bug fix wihout having explained the problem.&lt;/p&gt;
&lt;p&gt;I have observed it before that a PR process has discouraged continuous refactoring, though to be clear this is a problem with any process which includes a manual review, even the pair programming alternative from Dave Farley. The solution I have is the same as can be applied with any non-PR process, which is to encourage refactor and carve out a space for them. In a pair programming process, maybe allow refactors to just be pushed. In a PR process, I encourage my teams to allow &amp;quot;boyscout&amp;quot; PRs, which I named after the &lt;a href="https://code-specialist.com/code-principles/boy-scout-rule"&gt;Boy Scout Rule&lt;/a&gt;. The team treats these PRs specially and celebrates members who open such PRs. The broader point is that any process adopted by a team can encourage negative behaviors, so no matter the process your team should identify those patterns and alter the process to mitigate, eliminate, or reverse those.&lt;/p&gt;
&lt;h1&gt;Why PRs Are Good&lt;/h1&gt;
&lt;p&gt;As we can see, most of the problems brought up about PR processes aren't really problems with PR processes, but problems with communication, interpersonal dynamics, and the like. The same sorts of problems exist in any process, and while they might express themselves differently given different processes, I don't know that you can say they're &lt;em&gt;worse&lt;/em&gt; in any particular process. This is a problem I have with a lot of these hot takes: they would be more valuable if they actually addressed the root cause of the experiences described.&lt;/p&gt;
&lt;p&gt;The same should be true of any praise; if I want to convince you that PRs are good I should cite some unique advantages of them. I could say that I like PRs because they reinforce a code review culture, or because I like getting reviews from colleagues, but those are benefits of mandatory reviews and not PRs. What are the specific things that a PR process is good for? Well, this is easier asked than answered. Try to come up with a list of benefits in your head, then go over those and see if they're really benefits of pull requests specifically?&lt;/p&gt;
&lt;p&gt;I think the unique benefits then are all going to be related to tooling in some way. Most development tools familiar to most engineers are set up really well to work with pull requests. GitHub, GitLab, BitBucket, Azure DevOps, and plenty of others have really robust PR interfaces. Take any popular IDE and it's bound to have some level of integration with these PR features. The tooling and familiarity barrier with PRs are both very low, and this is an obvious benefit. &lt;em&gt;If&lt;/em&gt; your team is looking to some way of doing mandatory, pre-integration reviews, pull requests are &lt;em&gt;fantastic&lt;/em&gt; because of the ease of adoption.&lt;/p&gt;
&lt;p&gt;The tooling benefit extends to integrations with other tooling activities, too. Pull requests and, crucially, the tooling which supports them, are great at isolating specific changes and relating them to issue tracking, build automation, and documentation repositories. Being able to integrate so many parts of the tooling with the tooling that supports the code review process is a huge advantage over alternatives.&lt;/p&gt;
&lt;p&gt;Tooling is necessary to support PRs, I think. I'm not quite sure what a PR process without PR tooling would look like (but comment below if you've got thoughts on that). The tooling itself then has unique advantages facilitating communication and knowledge sharing. In certain contexts asynchronous communication is required, and if that applies to your team then pull requests offer a pretty good mechanism for async communication on the code review or even general convos directly related to the codebase. PR tooling also makes it incredibly easy for engineers or other observers to see what's going on in the system and the development process. Indeed there are ways other than PRs to achieve this, but the unique offer is the ease of getting all of it.&lt;/p&gt;
&lt;p&gt;One of my favorite benefits to PRs is a documentation aspect. When I open a PR I have to write out what I've done, then any conversation related to what I've done is written out in the PR until the end of time. Or, at least, until the end of my repository's life. It's a great way to be able to retrace steps later to get both the &lt;em&gt;what&lt;/em&gt; and the &lt;em&gt;why&lt;/em&gt;. PRs aren't the only way but, I think, the best way to achieve that benefit, at least with the current state of our tooling.&lt;/p&gt;
&lt;h1&gt;Should I Do PRs or Not?&lt;/h1&gt;
&lt;p&gt;If you like them then use them, and if you don't like them then don't do them, by all means try something else! I want to get across that the bulk of &amp;quot;PR bad&amp;quot; articles floating around the internets aren't a meaningful contribution to the discourse. If the issue is with mandatory pre-integration reviews, then pin the blame there. If the issue is with interpersonal or team dynamics, then address the solutions in that space. &lt;em&gt;That&lt;/em&gt; sort of analysis is what helps us make proper decisions about the conduct of our work. This popularity of terrible arguments creates a mini cacophony of almost-entirely-misdirected anti-PR sentiment - who is that good for?&lt;/p&gt;
&lt;p&gt;PRs are &lt;em&gt;fine&lt;/em&gt;. In fact, they have plenty of benefits which they share with the basic practices they're built on: feature branching, mandatory review, pre-integration review, and the like. PRs uniquely excel at some of those shared benefits, and the state of tooling decidedly sets PRs apart as the best way to get those benefits. Do those patterns and benefits apply to your team? Maybe so, maybe not. If you're on the fence about PRs or any other review process, I can't recommend enough &lt;a href="https://martinfowler.com/bliki/PullRequest.html"&gt;Martin Fowler's writing on the subject&lt;/a&gt;, in which he does a great job framing PRs in the context of &lt;a href="https://martinfowler.com/articles/branching-patterns.html"&gt;his branching patterns&lt;/a&gt;. I'd be curious in a similar analysis on review patterns, come to think of it.&lt;/p&gt;
&lt;p&gt;Maybe in 10 years the next GitHub will have come out and revolutionized tooling in some way that something other than PRs are so easy to adopt. Maybe it will offer the same benefits in integration with other tooling, communication, visibility, and documentation, or maybe it will offer different benefits that make it a great thing to use. That's a fun thing to consider - do I actually &lt;em&gt;need&lt;/em&gt; the benefits of PRs? Am I being impeded by feature branching or mandatory review?&lt;/p&gt;
</description>
      <pubDate>Wed, 18 Dec 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-12-18T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">question_of_the_week</guid>
      <link>https://ian.wold.guru/Posts/question_of_the_week.html</link>
      <title>Thing I Made: Question of the Week</title>
      <description>&lt;p&gt;I like asking dumb questions each week as a teambuilding thing; I made a site to show a random question each week: &lt;a href="https://questionoftheweek.io"&gt;QuestionOfTheWeek.io&lt;/a&gt;!&lt;/p&gt;
</description>
      <pubDate>Wed, 11 Feb 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-02-11T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">quick_and_dirty_sequential_ids_in_mongo</guid>
      <link>https://ian.wold.guru/Posts/quick_and_dirty_sequential_ids_in_mongo.html</link>
      <title>Quick &amp; Dirty Sequential IDs in MongoDB</title>
      <description>&lt;p&gt;That Mongo doesn't natively support sequential IDs is one of the many knocks against it. Sure, you &lt;em&gt;should&lt;/em&gt; be using GUID IDs in Mongo, but suppose you're working on a microservices conversion and you have a legacy mainframe that needs to be able to know what your objects are? If you're content just using Atlas, you can create a counter collection and add a trigger for auto-incrementing IDs &lt;a href="https://www.mongodb.com/basics/mongodb-auto-increment"&gt;fairly easily&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Suppose however that you can't use a pure Atlas solution - you'll need to implement this logic yourself in your own code. If you happen to be working in a microservices environment you have concurrency concerns - there might be multiple shards of your database and/or multiple replicas of your microservice.&lt;/p&gt;
&lt;p&gt;Is a primary key generator really the sort of thing you want &amp;quot;quick and dirty&amp;quot;? Probably not. Am I doing it in prod? Yes.&lt;/p&gt;
&lt;h1&gt;Updating a counter collection&lt;/h1&gt;
&lt;p&gt;As a prerequisite, ensure you have the Mongo driver:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;go get go.mongodb.org/mongo-driver/mongo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just as Mongo's tutorial for Atlas recommends, we'll implement a counter collection. This collection will contain one document per &amp;quot;kind&amp;quot; of ID we need to generate. If you have just one object that needs sequential IDs, then you'll only have one document in this collection. We'll represent this collection document with a struct. It only needs one field, &lt;code&gt;sequence&lt;/code&gt;, which will represent the latest ID generated:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;type MongoCounterDocument struct {
    sequence int `bson:&amp;quot;sequence&amp;quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ID of each document in the collection should be a string you hardcode or keep in a settings file (such as &lt;code&gt;&amp;quot;personIdCounter&amp;quot;&lt;/code&gt;), and doesn't need to be in the document struct. Instead, we'll encapsulate that in a generator struct along with a reference to the collection:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;type MongoIdGenerator struct {
    counterCollection *mongo.Collection
    counterDocumentId string
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To implement the functionality to generate the next ID, we'll use the &lt;code&gt;FindOneAndUpdate&lt;/code&gt; operation to increment &lt;code&gt;sequence&lt;/code&gt; and return the new ID to us. We can specify a couple options here: we can upsert the document so that it will be created automatically if one isn't there for us (useful for integration tests), and we can specify that we want the operation to read and return us a copy of the document &lt;em&gt;after&lt;/em&gt; the update has taken place.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func (generator *MongoIdGenerator) GetNextId() (int, error) {
    filter := bson.M{&amp;quot;_id&amp;quot;: m.counterDocumentId}
    update := bson.M{&amp;quot;$inc&amp;quot;: bson.M(&amp;quot;sequence&amp;quot;: 1)}
    options := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)

    var updatedDocument MongoIdCounter

    err := m.counterCollection.FindOneAndUpdate(context.TODO(), filter, update, options).Decode(&amp;amp;updatedDocument)
    if err != nil {
        return 0, errors.New(&amp;quot;Unable to update Mongo id counter collection.&amp;quot;)
    }

    return updatedDocument.sequence, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FindOneAndUpdate&lt;/code&gt; is atomic and shouldn't have any concurrency concerns so long as you &lt;strong&gt;do not shard the counter collection&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;But I don't want to have to hit Mongo every time I want a new id&lt;/h1&gt;
&lt;p&gt;Huh, you and I think alike, I didn't either! To get around this, we can have our app generate multiple IDs each time it hits Mongo and use these IDs until it runs out locally.&lt;/p&gt;
&lt;p&gt;With this approach you have the concern that if your app is spinning up and tearing down too frequently, you'll start losing IDs in the mix. There are various strategies to mitigate this, such as retrieving a small number of IDs from Mongo each time or persisting the cache of IDs, but I'm not going to get into those here.&lt;/p&gt;
&lt;p&gt;We'll add &lt;code&gt;nextId&lt;/code&gt; and &lt;code&gt;maxId&lt;/code&gt; properties to the generator object, as well as an increment field to specify how many IDs we should generate each time:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;type MongoIdGenerator struct {
    counterCollection *mongo.Collection
    counterDocumentId string
    incrementBy       int // [tl! ++]
    nextId            int // [tl! ++]
    maxId             int // [tl! ++]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We'll add a func to instantiate this at startup. It'll be important that your app only has one of these objects per &amp;quot;kind&amp;quot; of ID you need to generate:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func SetupMongoIdGenerator(collection *mongo.Collection, documentId string) *MongoIdGenerator {
    return $MongoIdGenerator{
        counterCollection   : collection,
        counterDocumentId   : documentId,
        // Adjust this up or down depending on how many IDs you want to generate at once:
        incrementBy         : 25,
        nextId              : 0,
        maxId               : 0
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can update our &lt;code&gt;GetNextId&lt;/code&gt; function to consult Mongo or not if &lt;code&gt;nextId&lt;/code&gt; equals &lt;code&gt;maxId&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func (generator *MongoIdGenerator) GetNextId() (int, error) {
    if generator.nextId == generator.maxId { // [tl! ++]
        filter := bson.M{&amp;quot;_id&amp;quot;: m.counterDocumentId}
        update := bson.M{&amp;quot;$inc&amp;quot;: bson.M(&amp;quot;sequence&amp;quot;: generator.incrementBy)}
        options := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)

        var updatedDocument MongoIdCounter

        err := m.counterCollection.FindOneAndUpdate(context.TODO(), filter, update, options).Decode(&amp;amp;updatedDocument)
        if err != nil {
            return 0, errors.New(&amp;quot;Unable to update Mongo id counter collection.&amp;quot;)
        }

        generator.nextId = updatedDocument.sequence - incrementBy // [tl! ++]
        generator.maxId = updatedDocument.sequence // [tl! ++]
    } // [tl! ++]

    return updatedDocument.sequence, nil // [tl! --]
    generator.nextId += 1 // [tl! ++]
    return generator.nextId, nil // [tl! ++]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We do have a concurrency concern here though - we want to ensure &lt;code&gt;nextId&lt;/code&gt; and &lt;code&gt;maxId&lt;/code&gt; are only being accessed one at a time. We can use a mutex in the generator for this. Update the generator:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;type MongoIdGenerator struct {
    counterCollection *mongo.Collection
    counterDocumentId string
    incrementBy       int
    nextId            int
    maxId             int
    mutex             sync.Mutex // [tl! ++]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add the following two to the beginning of &lt;code&gt;GetNextId&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func (generator *MongoIdGenerator) GetNextId() (int, error) {
    generator.mutex.Lock() // [tl! ++ **]
    defer generator.mutex.Unlock() // [tl! ++ **]

    if generator.nextId == generator.maxId {
        filter := bson.M{&amp;quot;_id&amp;quot;: m.counterDocumentId}
        update := bson.M{&amp;quot;$inc&amp;quot;: bson.M(&amp;quot;sequence&amp;quot;: generator.incrementBy)}
        options := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)

        var updatedDocument MongoIdCounter

        err := m.counterCollection.FindOneAndUpdate(context.TODO(), filter, update, options).Decode(&amp;amp;updatedDocument)
        if err != nil {
            return 0, errors.New(&amp;quot;Unable to update Mongo id counter collection.&amp;quot;)
        }

        generator.nextId = updatedDocument.sequence - incrementBy
        generator.maxId = updatedDocument.sequence
    }

    generator.nextId += 1
    return generator.nextId, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should be that! Here's the final code all together:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;type MongoCounterDocument struct {
    sequence int `bson:&amp;quot;sequence&amp;quot;`
}

type MongoIdGenerator struct {
    counterCollection *mongo.Collection
    counterDocumentId string
    incrementBy       int
    nextId            int
    maxId             int
    mutex             sync.Mutex
}

func SetupMongoIdGenerator(collection *mongo.Collection, documentId string) *MongoIdGenerator {
    return $MongoIdGenerator{
        counterCollection   : collection,
        counterDocumentId   : documentId,
        incrementBy         : 25,
        nextId              : 0,
        maxId               : 0
    }
}

func (generator *MongoIdGenerator) GetNextId() (int, error) {
    generator.mutex.Lock()
    defer generator.mutex.Unlock()

    if generator.nextId == generator.maxId {
        filter := bson.M{&amp;quot;_id&amp;quot;: m.counterDocumentId}
        update := bson.M{&amp;quot;$inc&amp;quot;: bson.M(&amp;quot;sequence&amp;quot;: generator.incrementBy)}
        options := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)

        var updatedDocument MongoIdCounter

        err := m.counterCollection.FindOneAndUpdate(context.TODO(), filter, update, options).Decode(&amp;amp;updatedDocument)
        if err != nil {
            return 0, errors.New(&amp;quot;Unable to update Mongo id counter collection.&amp;quot;)
        }

        generator.nextId = updatedDocument.sequence - incrementBy
        generator.maxId = updatedDocument.sequence
    }

    generator.nextId += 1
    return generator.nextId, nil
}
&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Wed, 01 Nov 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-11-01T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">reclaim_your_agile</guid>
      <link>https://ian.wold.guru/Posts/reclaim_your_agile.html</link>
      <title>Reclaim Your Agile: The One Clever Trick Agile Coaches Don't Want You to Know</title>
      <description>&lt;p&gt;This is the one clever trick that Agile coaches don't want you to know: you &lt;em&gt;can&lt;/em&gt; actually become an Agile team. There is actually a light at the end of this tunnel. Okay, maybe I'm being a bit fanciful. However, there is one thing that I've done on several teams now that has led them to be able to reclaim their development process and become more Agile. With this &lt;em&gt;one clever trick&lt;/em&gt; your team can create and dictate its own process. Again, perhaps a bit fanciful, but like I said this has worked for me and it might be beneficial to you too in some way.&lt;/p&gt;
&lt;p&gt;I've heard a lot of my colleagues say that they are skeptical of, or even dislike, Agile. In almost every single case, I think they were actually talking about Scrum. It's a forgiveable mistake, these two words are largely used interchangeably these days. However, I encourage you to read the &lt;a href="https://agilemanifesto.org/principles.html"&gt;Agile Principles&lt;/a&gt; and any piece of the &lt;a href="https://www.scrum.org/resources/scrum-guide"&gt;Scrum Guide&lt;/a&gt; and tell me that they're the same thing. They're not. Scrum is almost entirely antithetical to Agile (&amp;quot;almost&amp;quot; in this case being around the 99.99% range), designed to satisfy corporations who aren't quite ready to give up their waterfall yet. Agile is the emphasis on early and continuous delivery of value to the customer, whose experience is the first and foremost concern, and the prescription that each &lt;em&gt;engineering&lt;/em&gt; team must create, own, and direct its own process.&lt;/p&gt;
&lt;p&gt;I do think that the Agile principles are the best set of principles we've come up with so far to try to understand how to best develop software, and I want to tell you about this way I've found that's helped a team or two become more truly Agile.&lt;/p&gt;
&lt;p&gt;It started by accident actually, many years ago. I had just been hired by Dalex Livestock Solutions, a small agricultural firm, to my first properly employed position (I was an independent consultant before that). They were a full 5 people strong, and we had a meeting in Minneapolis the week I was hired to chart the future course - they wanted to invest in developing new products (that's why I was brought on). Being a 4-person company up to that point, they had never thought of their development process. However, it was clear to them that if they wanted to scale, they'd need some process. They said they wanted Scrum, and I obliged them and went along with it. There was one hiccup though - none of the non-engineers there understood a single thing that the engineers were saying about scrum. The vocabulary was a stumbling block - terms like &amp;quot;scrum meeting&amp;quot; and &amp;quot;sprint&amp;quot; were more puzzling than helpful. And funnily enough, having a former rugby captain on the team didn’t make translating these terms any easier!&lt;/p&gt;
&lt;p&gt;In a stroke of creativity (though I can't recall exactly whose), we decided to give a fresh spin to these scrum terms. Being in the agriculture domain, we thought of using old rancher terms to describe the concepts. This caught their attention, and we ended up using these words the entire five years I was there. We didn't have &amp;quot;sprints&amp;quot;, we had &amp;quot;cattle drives&amp;quot;. No &amp;quot;sprint planning&amp;quot;, that was a &amp;quot;saddle up&amp;quot;; &amp;quot;retrospectives&amp;quot; were &amp;quot;circling the herd&amp;quot;; instead of a &amp;quot;backlog&amp;quot; we had a &amp;quot;haystack&amp;quot;; and so on. Not one of the words we used are in the scrum guide. Over the years, when we felt we needed to make a change, we would never be bogged down with questions like &amp;quot;What does the Scrum guide recommend?&amp;quot;, but instead we'd ask &amp;quot;Well, what actually &lt;em&gt;is&lt;/em&gt; a 'Cattle Drive'?&amp;quot;. After just a couple of years we had drifted away from Scrum entirely and into something of our own - something actually &lt;em&gt;Agile&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;My takeaway was that by redefining and taking control over our &lt;em&gt;language&lt;/em&gt;, we were able to expand on that to take control of our &lt;em&gt;process&lt;/em&gt;, and I think this works - in different ways - for teams of any size. Maybe this approach will work for you, maybe it won't, but it seems to me like it's a chance for many teams to be able to fight against their Scrums and make it something actually Agile, so I want to share some ideas for how you can actually do this.&lt;/p&gt;
&lt;h1&gt;Just Start With Words&lt;/h1&gt;
&lt;p&gt;Some industries are better suited to this than others, but the central, most important, idea is to use your own words for each part of your process. I gave you some agriculture examples, but you can find examples in your own industry. Or suppose you work in insurance or banking where the domain is very dry. Do you all enjoy superheroes or Star Trek? Use Star Trek words! Find something that unites your team.&lt;/p&gt;
&lt;p&gt;At the agricultural firm, I started with &lt;em&gt;all of the words&lt;/em&gt; but you certainly don't have to. Start with one element or ritual and make a simple change. Given the &amp;quot;backlog&amp;quot; was the &amp;quot;haystack&amp;quot; to us, I just renamed a list on the kanban board. &amp;quot;Isn't that funny!&amp;quot; they'd exclaim. Reinforce this change with the way you speak, custom Slack emojis, or anything else to give your team a special color. When someone calls it the &amp;quot;backlog&amp;quot; errantly, call them out: &amp;quot;Don't you mean the 'haystack'?&amp;quot;&lt;/p&gt;
&lt;p&gt;Everyone - even business - will be on board with you if you pick your first target right. They'll be impressed how your team is taking ownership over its work. If you work at a place that's too corporatish, brag about how the name change helps align the synergy of your deliveries or something of the sort.&lt;/p&gt;
&lt;h2&gt;Keep it Fun&lt;/h2&gt;
&lt;p&gt;Don't jump right in trying to redefine everything, and don't show up at retro one day ready to rock with &amp;quot;Hey team, I've redefined all of our words - &lt;em&gt;isn't this great&lt;/em&gt;?&amp;quot;. No, it's not great, not for your teammates. Make a suggestion - maybe start talking to one or two colleagues to start: &amp;quot;Wouldn't it be &lt;em&gt;fun&lt;/em&gt; to rename this-or-that process to &lt;em&gt;(insert word here)&lt;/em&gt;?&amp;quot;&lt;/p&gt;
&lt;p&gt;You want to create an environment where everyone is looking forward to discussing words. When your team, probably jokingly, chooses a new word for a process - stick to it. Use it everywhere and, when appropriate, correct coworkers when they use the &amp;quot;wrong&amp;quot; word. This isn't something rigid I'm trying to prescribe, and in practice it's a natural way to have a bit of a laugh among colleagues.&lt;/p&gt;
&lt;p&gt;You might have coworkers who wouldn't want a change in name - they might be too uptight, too set in their ways, or too anything else. More likely than anything, they might just not understand you or the idea. Go slow, keep it light, and emphasize the benefit of team cohesion. You'll win over even the most ardent colleagues.&lt;/p&gt;
&lt;h2&gt;Press the Advantage&lt;/h2&gt;
&lt;p&gt;Expand that to other parts of the process, and engage everyone on your team. At the end of a retrospective, suggest a new thing to rename. Can your team take five minutes to brainstorm a new name for this or that meeting? Sure they can!&lt;/p&gt;
&lt;p&gt;Engender a lively and friendly debate - which word is the more &amp;quot;agricultural&amp;quot; or &amp;quot;Star Trek&amp;quot; or whatever word for this concept? Which one does the whole team like? Remember that &lt;em&gt;you&lt;/em&gt; don't need to like the words, the team does. Keep them happy.&lt;/p&gt;
&lt;p&gt;Before too long, you'll have your own words for everything. Your team will own its own language, and language is everything. Anybody who will want to redirect your team and change its processes will need to understand your lingo now.&lt;/p&gt;
&lt;h2&gt;Entrench Your Position&lt;/h2&gt;
&lt;p&gt;Everyone who deals with your team needs to know its lingo now. You want us to make a change to our microservice? You better say its name right! That's correct, the microservice is called &amp;quot;Khan&amp;quot; because engaging with it will entrench your team in a days-long battle deep in a dangerous nebula that will drain everyone's willpower until one of you can assert a week dominance over the other and finally destroy it (such is the world of microservice development).&lt;/p&gt;
&lt;p&gt;Do not relent. Your language is your power. If only your team speaks this language, then you've got a special flavor and nothing else. If your team plus everyone interacting with your team speaks this language, then you've got a real power.&lt;/p&gt;
&lt;p&gt;Inevitably, you'll have colleagues on other teams get confused or maybe frustrated at all your different words. Avoid confrontation and fall back to the old words if you need to in these contexts, but once again you'll bridge this gap if you keep it fun and take it slow. Don't create a glossary on your team's Confluence page, and even if you do don't refer folks from other teams there. Just use the new lingo in casual communication and laugh with yourself and your team when others find it strange. You'll win them all over too, with time and good nature.&lt;/p&gt;
&lt;h1&gt;Start Changing Things&lt;/h1&gt;
&lt;p&gt;Now that you have real power, use it. At retrospectives, stop asking what the right name for something is - you've already named everything. Start asking &amp;quot;What actually &lt;em&gt;is&lt;/em&gt; a 'Cattle Drive'?&amp;quot;.&lt;/p&gt;
&lt;p&gt;At first, your teammates will be confused - there's no longer a Scrum guide to fall back on! Nobody knows what a 'Cattle Drive' is - it's not in the Scrum guide, it's not in any guide! This is when the real thinking comes out. I guarantee, buried deep in your team is a wealth of knowledge, and by removing the onerous cap of the Scrum guide you'll discover it.&lt;/p&gt;
&lt;p&gt;The hope at this point is that by having divorced your team from the language of Scrum, you can change the process by talking about your new words instead of talking about process. This is another area that can't be rushed, and you might be surprised by what the first candidate is. Before too long, you'll surely have a sprint (or, perhaps a &lt;em&gt;cattle drive&lt;/em&gt;) where everyone on the team has been impeded by a particular process. Motivate a conversation here about this process, but use your new word and avoid talking about process.&lt;/p&gt;
&lt;p&gt;Keeping your team entrenched in the new language keeps the team away from considering &amp;quot;what does the Scrum Guide say about this process?&amp;quot; because the Scrum Guide says nothing about &lt;em&gt;haystacks&lt;/em&gt; or &lt;em&gt;saddle ups&lt;/em&gt;. Make the first process change on a process that everyone agrees needs to be changed when they are all agreeing that it needs to be. After the first change, others will be easier.&lt;/p&gt;
&lt;h2&gt;Incorporate Ideas&lt;/h2&gt;
&lt;p&gt;There will be a lot of ideas suggested. &amp;quot;'Circling the herd' should be &lt;em&gt;X&lt;/em&gt;&amp;quot; or &amp;quot;A 'stampede' should be &lt;em&gt;Y&lt;/em&gt;&amp;quot;. Create a framework for trying ideas: agree to try ideas for a time, hold rigidly to them, then after that period of time reevaluate. Did the idea work? Keep it. Did it fail? Discard it. Something in-between? Adapt. Again: &lt;em&gt;you&lt;/em&gt; don't need to like the ideas here. Rather, &lt;em&gt;someone&lt;/em&gt; does, and that's the indicator of success.&lt;/p&gt;
&lt;p&gt;When new ideas are incorporated, your process changes. Don't bill this as a process change. Instead, it's an update to a word that others don't fully understand anyway. After all, other teams are talking about your team in terms of &amp;quot;cattle drives&amp;quot; or whatever. They don't know what that is - you can tell them, and you can tell them different things months apart, just make sure you're approachable - you'll be surprised how far you can stretch your process, and somehow others will be able to work with you &lt;em&gt;because humans tend to be rather adaptable&lt;/em&gt;. Don't worry here, just own your process. Whatever your words are, they're your team's words.&lt;/p&gt;
&lt;p&gt;Some might yet object to new ideas because the almighty Scrum Guide doesn't contain it. You can jokingly assert that the Scrum Guide says nothing about your new word(s), but your team should be sufficiently motivated to make changes when they're considering them. If you lose a battle that's okay, you're in it for the long haul to win the war. Consensus and buying in is crucial to making fundamental changes.&lt;/p&gt;
&lt;h2&gt;Keep the Team in Control&lt;/h2&gt;
&lt;p&gt;Inevitably, outsiders will try to push on you. They'll try to compare a Cattle Drive to a sprint or other lame things like that. Don't let them. Ensure everyone on your team is on the same page so they can answer uniformly: &amp;quot;A 'cattle drive' is a set time in which we can develop a small set of features that directly benefit the farmers.&amp;quot;. They might say that that sounds like a sprint - insist it's not and redirect back to how it satisfies the customers or some corporate metric.&lt;/p&gt;
&lt;p&gt;Always redirect to your team. Start from the position that your team owns its language; anything different is &lt;em&gt;entirely foreign&lt;/em&gt; to you. Don't speak about Scrum, just speak from the position of your team. This is how we do things, &lt;em&gt;this is the way&lt;/em&gt;. A random person can go pretty far in an office if they're wearing a tie and carrying a clipboard, right?&lt;/p&gt;
&lt;h1&gt;Become Agile&lt;/h1&gt;
&lt;p&gt;You now own your own processes, and your team has a regular way of changing its process to respond to the environment it's in. You're now Agile, but remember it, keep it up, and don't relent.&lt;/p&gt;
&lt;p&gt;When your process needs to change, change it but stick to your team's vocabulary. Deliver customer value early and often, and find ways to get your team as close to the customer as you can. If your engineers can regularly interact directly with the customer, that's the best. You can always fall back on the Agile manifesto and principles to give some guidance on how you might approach sharpening your process, but remember that these are only a guide and your team needs to discover, implement, and own its own process and methodologies for its own environment.&lt;/p&gt;
&lt;h2&gt;When in Doubt, Rip it Out&lt;/h2&gt;
&lt;p&gt;Most teams, especially those coming from Scrum, have too much process. A lot of them also don't have enough communication - even teams that are mostly in-office with each other. When it comes time to reevaluate a practice that you and your team have, consider ripping it out - stop the practice. It might seem unintuitive, but a lot of things in our industry are. If a particular element of your process is getting in the way somehow, maybe just remove it and &lt;em&gt;get it out of the way&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Can the problem be solved with more communication? If you're in office do you need to adopt a better culture of collaboration throughout the day? If you're distributed, do you need to encourage more ad hoc video calls? If you can reduce the barrier to communication and encourage as much communication as possible, surely you'll find no more need for meetings to &amp;quot;align stakeholders&amp;quot; or the like.&lt;/p&gt;
&lt;p&gt;Sometimes it can be difficult to rip out a process even when it's clear it needs to go. You might have colleagues who are too familiar with this process, or maybe colleagues who think they don't want to communicate that often. This is a collaborative process, and they need to be comfortable and along for the ride too, but focus on the benefit and keep the conversations focused on what your team and its product need. You can always lead by being the change you wish to see - if more video calls are needed, start hashing your problems out over video call with your colleagues.&lt;/p&gt;
&lt;h2&gt;Drive Change From Product Quality&lt;/h2&gt;
&lt;p&gt;We're in the industry of developing software products, so the working of the software that we deliver to our customers is always, invariably, the primary measure of our success. Our processes need to be honed to delivering valuable, functioning software, and so the primary impetus to change our processes should be from the metrics of our product's quality. You (probably) don't need two hundred different charts and graphs measuring every last metric you can think of, but your team does need to have agreed metrics and definitions of quality.&lt;/p&gt;
&lt;p&gt;The greatest opportunity for change is when one of these metrics is underperforming. It's easy to orient the team around wanting to solve this issue, and often quality issues are largely - or at least in part - a fault of something in the process.&lt;/p&gt;
&lt;h2&gt;Involve the Customer&lt;/h2&gt;
&lt;p&gt;The gold standard for a software engineering process is, in my experience and humble opinion, to have the customer right beside you as you develop your software. I spent the first several years of my career as a consultant developing LOB software, and I had the opportunity to develop software in this way. Not only does it eliminate a whole swath of processes just to get me customer feedback; I have the most reliable feedback and best customer experience as a result.&lt;/p&gt;
&lt;p&gt;This is not realistically achievable for all teams though - I work in ecommerce right now and I can't imagine what it would look like to have actual customers next to me. Would they even care? Instead, we have a team that collects customer feedback and records interviews, and I can go and read or watch those whenever I need - we've developed a process that works for us and gets me as close to the customer as possible.&lt;/p&gt;
&lt;p&gt;Always consider the customer as you develop your processes - where do they fit in and how do you and your colleagues know &lt;em&gt;who&lt;/em&gt; they actually are? If you can realize efficiencies here, you can probably eliminate significant parts of your process.&lt;/p&gt;
&lt;h2&gt;You Won't Ever Finish&lt;/h2&gt;
&lt;p&gt;The only constant, especially in our industry, is &lt;em&gt;there are no constants&lt;/em&gt;. In a way this can be demoralizing; we can never create the one perfect bit of software or the one true process. But that's the environment we operate in, and we need to accommodate it. That's part of what makes Agile necessary to me, or at least that you and your team need to be empowered to develop and own their own processes.&lt;/p&gt;
&lt;p&gt;Your process is never going to be set and it will constantly change. Keep it changing, and keep ownership over your process. To get back to my thesis here, keep the language yours. Don't let others impose their ideas or their own language on you, play uno reverse. When things need to change, this is a process to introduce new fun words or to recontextualize them to fit new problems. This process of using your own language makes it all the more fun and meaningful for teams, and will often lead to better - maybe, more &lt;em&gt;agile&lt;/em&gt; - results.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;There are no silver bullets, but this process has worked incredibly well for me, and it might for you as well. At the heart of it, the idea is to get your team to own its own language and exploit that as an advantage to have its own process. Confuse your superiors with funny words and slip your own process in below that.&lt;/p&gt;
&lt;p&gt;You might also think that you don't actually want your team to be 'Agile'. If you feel that way, are you actually talking about Scrum? Agile and Scrum are very different. Read the Agile Manifesto then the Scrum Guide. Do these seem like they're saying the same thing? They're not - Scrum is a completely disingenuous bastardization of Agile for the benefit of corporate environments. Agile, on the other hand, is a loose set of guides that don't prescribe a specific system - they want to empower your team to develop its own system.&lt;/p&gt;
&lt;p&gt;That's what I want to get at with this article. I want to give you one way, doubtlessly among many ways, to reclaim your process. Reclaim your 'Agile' and form something that works for you. I do think that the Agile principles must be followed: deliver value early and often, stay close to your customers, and always reevaluate.&lt;/p&gt;
&lt;p&gt;On top of all of this, keep learning and keep your mind open to new ideas in process. There's many different ideas - some good, many iffy - about how to develop software. Ultimately, the best process for your team isn't written anywhere. Only by trying new things out can your team find a process that works the best.&lt;/p&gt;
&lt;p&gt;I hope all of your future cattle drives - or whatever word you settle on - are filled with productive enhancements for the users' benefit.&lt;/p&gt;
</description>
      <pubDate>Wed, 13 Dec 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-12-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">roll_your_own_csharp_results</guid>
      <link>https://ian.wold.guru/Posts/roll_your_own_csharp_results.html</link>
      <title>Roll Your Own C# Results</title>
      <description>&lt;p&gt;The result pattern is one of my favorite functional patterns to use in the C# world, and I don't think I'm alone on this. If you try to Google &amp;quot;result pattern&amp;quot; to try to find a Wikipedia article or something to link to as a definition of the pattern, you'll find a bunch of articles and repositories explaining the pattern in C#. Maybe that's just my search results, but this pattern is such a fundamental concept in functional programming that I imagine most folks regularly using functional languages might scoff at the suggestion that this should be elevated to the level of being a proper design pattern.&lt;/p&gt;
&lt;p&gt;I'd like to consider it a proper pattern though. Typically and traditionally our C# programs handle errors by throwing and catching exceptions. Exceptions are abused in this case though - they're intended for truly &lt;em&gt;exceptional&lt;/em&gt; events, occurrances which I don't expect to happen and which absolutely necessitate the interruption of my control flow. If your API's validation layer found a validation error in a request body, that's not exceptional because I expect clients to send invalid requests time-to-time. If my application wasn't able to parse a JSON response from an external resource, that's not exceptional because I know that data outside my application is never 100% trustworthy.&lt;/p&gt;
&lt;p&gt;Nonetheless, we have all tended at one point to treat them as exceptional and throw exceptions in these cases. This can lead to bad code in a number of ways - I might forget to catch an exception at some point, I might not consider all of the error states I need to support for an operation, or my application might be made slow by excessively throwing exceptions. Instead, it's proper to only throw exceptions when the application has entered a state which we either consider impossible or from which I know it is impossible to recover. In the other 99% of cases, I want to return a result object representing the success of the operation. This is the result pattern.&lt;/p&gt;
&lt;h1&gt;Existing Implementations in C#&lt;/h1&gt;
&lt;p&gt;There's a number of libraries you can find on GitHub to support this in C#. Two well-liked ones are &lt;a href="https://github.com/ardalis/Result"&gt;ardalis' Result library&lt;/a&gt; and &lt;a href="https://github.com/altmann/FluentResults"&gt;altmann's FluentResult library&lt;/a&gt;. You'll notice that these libraries support a method returning one of a set of options - Success, Failure, Invalid, etc. The Computer Science term for this is a &lt;a href="https://en.wikipedia.org/wiki/Tagged_union"&gt;tagged union&lt;/a&gt;, also called a discriminated union or an algebraic data type. These types are foundational to functional programming and the basis of the result pattern, but not yet supported in C#. &lt;a href="https://github.com/mcintyre321/OneOf"&gt;The OneOf library&lt;/a&gt; is an attempt to provide a generic form of these in C#, with which you can implement a result pattern.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Note that the C# team is actively working to include discriminated unions in the langauge!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Those and others are all fine libraries, and while each of them can solve specific scenarios quite well none of them are a one-size-fits-all approach. If you just need &amp;quot;success&amp;quot; and &amp;quot;failure&amp;quot;, then you probably just need one of these libraries or you can write your own class which has an &lt;code&gt;IsSuccess&lt;/code&gt; flag. However, in a lot of cases we have more than one kind of result we might need to return. A validation layer might have multiple levels of validation (valid, valid but for an old version, invalid), or a persistence layer might want to distinguish between multiple states (found all the objects you queried, the query succeeded but yielded no results, the query itself was invalid, the parameters were invalid, the database crashed, etc).&lt;/p&gt;
&lt;p&gt;Among the libraries which support more than 2 result states, &lt;code&gt;OneOf&lt;/code&gt; is the best option here, but you'll find that you need to use &lt;code&gt;T0&lt;/code&gt;, &lt;code&gt;T1&lt;/code&gt;, &lt;code&gt;T2&lt;/code&gt;, and so on in your code; this is due to the generic nature of the library. To get around that you'll need to implement your own classes on top of it, which may be what you need but I'd stop here and ask: why do we not roll our own? No, it's not complicated, in fact it's very easy and probably involves no more code than if you were to extend &lt;code&gt;OneOf&lt;/code&gt; anyway. Then of course you get the benefit that your solution is tailored to your problem and your team can extend and maintain it as your application evolves.&lt;/p&gt;
&lt;p&gt;There's a lot of ways to get to where we want to be, and one might be more preferable for your case. I'm going to demonstrate two: first a &amp;quot;quick and dirty&amp;quot; implementation using one class with a result flag, and second a (more preferable, in my opinion) solution using multiple classes and implicit casting. For both examples I'll implement a database query result which for the purposes of this article can either be Found, Empty, ClientError, or ServerError. If it is Found, it will contain the object which we queried; if it is one of the two errors, it will contain a string description of the error.&lt;/p&gt;
&lt;h1&gt;First Approach: One Class, Result Flag&lt;/h1&gt;
&lt;p&gt;The simplest way to get off the ground with a result object is to implement one class with an enum flag signifying what type of result it is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public enum DatabaseQueryResultType
{
    Found,
    Empty,
    ClientError,
    ServerError
}

public class DatabaseQueryResult
{
    public DatabaseQueryResultType Type { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;DatabaseQueryResult&lt;/code&gt; will also need to hold the value of the result for the &lt;code&gt;Found&lt;/code&gt; case, as well as the error description for either of the error cases. These need to be nullable as we can't know what kind of result this is until runtime. The class also needs to be generic as we need to support different types of values:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    public DatabaseQueryResultType Type { get; set; }

    public T? Value { get; set; }

    public string? ErrorDescription { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be used as-is now:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt; GetAllItemsFromDatabase(...)
{
    try
    {
        var result = ContactDatabaseSomehow(...);

        if (result is IEnumerable&amp;lt;Item&amp;gt; items)
        {
            return new DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;
            {
                Type = DatabaseQueryResultType.Found,
                Value = items
            };
        }
    }
    catch (/*exception indicating client error*/)
    {
        return new DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;
        {
            Type = DatabaseQueryResultType.ClientError,
            ErrorDescription = exception.Message
        };
    }
    catch (/*exception indicating server error*/)
    {
        return new DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;
        {
            Type = DatabaseQueryResultType.ServerError,
            ErrorDescription = exception.Message
        };
    }

    return new DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;
    {
        Type = DatabaseQueryResultType.Empty
    };
}

public void ShowItems()
{
    var result = GetAllItemsFromDatabase(...);
    switch (result.Type)
    {
        case DatabaseQueryResultType.Found:
            foreach (var item in result.Value!)
            {
                Console.WriteLine(item.ToString());
            }
            break;
        case DatabaseQueryResultType.Empty:
            Console.WriteLine(&amp;quot;No items found!&amp;quot;);
            break;
        case DatabaseQueryResultType.ClientError:
        case DatabaseQueryResultType.ServerError:
            Console.WriteLine($&amp;quot;Error! {result.ErrorDescription}&amp;quot;);
            break;
        default:
            throw new Exception(&amp;quot;This is an actual exceptional state since the code _should_ never get here.&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works but oh goodness the verbosity! The most glaring verbosity issue, at least as I perceive things, is all the code we spend instantiating a new &lt;code&gt;DatabaseQueryResult&lt;/code&gt; each time. Imagine if every single method in the persistence layer was littered with this code - not only is it taking up a lot of space but we're relying on the implementor to perfectly instantiate the &lt;code&gt;DatabaseQueryResult&lt;/code&gt; each time - they need to know to &lt;em&gt;always&lt;/em&gt; set the &lt;code&gt;Value&lt;/code&gt; if it's found, to &lt;em&gt;always&lt;/em&gt; set the &lt;code&gt;ErrorDescription&lt;/code&gt; for an error, and so on. We can implement a couple of static instantiator methods to guard this and reduce the size of the implementing code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    private DatabaseQueryResult() { }

    public DatabaseQueryResultType Type { get; private set; }

    public T? Value { get; private set; }

    public string? ErrorDescription { get; private set; }

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Found(T Value) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Found
        Value = value
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Empty() =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Empty
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ClientError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ClientError,
        ErrorDescription = description
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ServerError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ServerError,
        ErrorDescription = description
    };
}

public DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt; GetAllItemsFromDatabase(...)
{
    try
    {
        var result = ContactDatabaseSomehow(...);

        if (result is IEnumerable&amp;lt;Item&amp;gt; items)
        {
            return DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;.Found(items);
        }
    }
    catch (/*exception indicating client error*/)
    {
        return DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;.ClientError(exception.Message);
    }
    catch (/*exception indicating server error*/)
    {
        return DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;.ServerError(exception.Message);
    }

    return DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;.Empty();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That simplifies and safety-ifies (that's not a word) our implementing code a lot. There's still a similar problem in the code which consumes the result object, but to a lesser degree. The switch statement is a bit cumbersome, and it's annoying to have to implement the default case always. Here's the first instance where I can rely on the nature of our specific use case to get some custom value that I might have a bit more difficulty with a library. Although I have 4 result states, for most purposes I only really care about three - did it succeed, did it fail, or is it empty? I can implement two methods - one to check for success and one to check for failure - then use an easier-to-read if/else if/else check to get the behavior I want:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    private DatabaseQueryResult() { }

    public DatabaseQueryResultType Type { get; private set; }

    public T? Value { get; private set; }

    public string? ErrorDescription { get; private set; }

    public bool IsFound() =&amp;gt;
        Type == DatabaseQueryResultType.Found;

    public bool IsError() =&amp;gt;
        Type == DatabaseQueryResultType.ClientError
        || Type == DatabaseQueryResultType.ServerError;

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Found(T Value) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Found
        Value = value
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Empty() =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Empty
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ClientError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ClientError,
        ErrorDescription = description
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ServerError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ServerError,
        ErrorDescription = description
    };
}

public void ShowItems()
{
    var result = GetAllItemsFromDatabase(...);

    if (result.IsFound())
    {
        foreach (var item in result.Value!)
        {
            Console.WriteLine(item.ToString());
        }
    }
    else if (result.IsError())
    {
        Console.WriteLine($&amp;quot;Error! {result.ErrorDescription!}&amp;quot;);
    }
    else
    {
        Console.WriteLine(&amp;quot;No items found!&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This takes up much less space and, at least to me, reads much clearer with a better understanding of the intention of the code. Ive seen a lot of implementations stop here, but there's one more nagging bit - null checks! I &lt;em&gt;know&lt;/em&gt; that when the result is &lt;code&gt;Found&lt;/code&gt; that &lt;code&gt;Value&lt;/code&gt; is going to be non-null, and the same for &lt;code&gt;ErrorDescription&lt;/code&gt; when the type is one of the error types. It's annoying for the consumer to have to spam &lt;code&gt;!&lt;/code&gt; everywhere when it knows that these values are not null, and it's bad practice anyway to not encapsulate this behavior.&lt;/p&gt;
&lt;p&gt;In this case I can implement the &lt;code&gt;IsFound&lt;/code&gt; and &lt;code&gt;IsError&lt;/code&gt; methods to return the non-null values with a sort of modified try pattern, which would also allow me to hide the internal state of the result object from the outside world. This is much better design, and it pains me when this simple step isn't taken. I'll just provide a simple implementation of these two methods, yours might involve more checks depending on your case:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    private DatabaseQueryResult() { }

    private DatabaseQueryResultType Type { get; set; }

    private T? Value { get; set; }

    private string? ErrorDescription { get; set; }

    public bool IsFound([NotNullWhen(true)] out T? value)
    {
        value = Value;
        return Type == DatabaseQueryResultType.Found;
    }

    public bool IsError([NotNullWhen(true)] out string? description)
    {
        description = ErrorDescription;
        return
            Type == DatabaseQueryResultType.ClientError
            || Type == DatabaseQueryResultType.ServerError;
    }

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Found(T Value) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Found
        Value = value
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; Empty() =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.Empty
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ClientError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ClientError,
        ErrorDescription = description
    };

    public static DatabaseQueryResult&amp;lt;T&amp;gt; ServerError(string description) =&amp;gt; new()
    {
        Type = DatabaseQueryResultType.ServerError,
        ErrorDescription = description
    };
}

public void ShowItems()
{
    var result = GetAllItemsFromDatabase(...);

    if (result.IsFound(out var items))
    {
        foreach (var item in items)
        {
            Console.WriteLine(item.ToString());
        }
    }
    else if (result.IsError(out var description))
    {
        Console.WriteLine($&amp;quot;Error! {description}&amp;quot;);
    }
    else
    {
        Console.WriteLine(&amp;quot;No items found!&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By implementing proper OO standards here, we've encapsulated everything about the operation of the result classes and we're exposing only exactly what's necessary. This makes it trivially simple to implement and I don't need a lot of documentation around here - most anyone could jump into the code here and implement a consumer of this result conforming to the proper standards. As a bonus, you should also move the enum definition inside the class and make it private. In addition, if your consumers care about the distinction between client and server errors, you'll want to implement some way of being able to discern that - extra &lt;code&gt;IsClientError&lt;/code&gt; and &lt;code&gt;IsServerError&lt;/code&gt; methods would allow consumers to choose whether they care about &lt;em&gt;any&lt;/em&gt; error case or if they case about &lt;em&gt;specific&lt;/em&gt; error cases.&lt;/p&gt;
&lt;p&gt;There's another important point here that often goes overlooked in result implementations: it's &lt;em&gt;impossible&lt;/em&gt; for me to get the value of the result without performing a success check at the same time. Often those who encourage using the result pattern in C# focus on the negative aspects of throwing exceptions more than the positive effects of forcing success checks; indeed, this is because a lot of result implementations in C# don't force these checks. However, this is an essential - maybe &lt;em&gt;the&lt;/em&gt; essential - part of this pattern. It helps to put you and your team in the pit of success by forcing proper (or at least better) explicit error case handling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you take nothing else away from this article, take away the above paragraph: Always use the result pattern to enforce proper error handling.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There's one bit yet that annoys me about this code, and I think we can do better. However, it's going to force us to rewrite our solution here. My problem is the code which produces one of these results - in the empty or error cases, I still need to write out the full return type even though I don't have one! This seems like a minor inconvenience but look at the code above - if your objects are named longer that's a lot of eyesore. Wouldn't it be nice if there was some type inference to write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt; GetAllItemsFromDatabase(...)
{
    try
    {
        var result = ContactDatabaseSomehow(...);

        if (result is IEnumerable&amp;lt;Item&amp;gt; items)
        {
            return DatabaseQueryResult.Found(items);
        }
    }
    catch (/*exception indicating client error*/)
    {
        return DatabaseQueryResult.ClientError(exception.Message);
    }
    catch (/*exception indicating server error*/)
    {
        return DatabaseQueryResult.ServerError(exception.Message);
    }

    return DatabaseQueryResult.Empty();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being clear, that won't compile now. Instead, we'll look at a second approach.&lt;/p&gt;
&lt;h1&gt;Second Approach: Multiple Classes, Implicit Casting&lt;/h1&gt;
&lt;p&gt;This approach is going to get us closer to how this pattern tends to be used in actual functional languages, and it's much closer to how I assume we'll be coding once the C# team adds discriminated unions to the language. With this approach, we're going to create one record per each result type, then use implicit casting on a generic base type to allow instances of these records to instantiate the base type. This will allow methods to use the base type as a return type. This base class will contain the flags and values and &lt;code&gt;isFound&lt;/code&gt; checks and everything we had before, these extra classes will just be giving us a nicer way to instantiate and think about our results.&lt;/p&gt;
&lt;p&gt;Why not use inheritance? Because that would not get me away from having to specify the generic type for all result values. You can certainly implement a result object using inheritance, but I don't think that the majority of use cases benefit from it.&lt;/p&gt;
&lt;p&gt;Though we could extend the first approach, I'm going to start from the ground up here for the sake of simplicity. The first thing I'm going to do is to define all of my result types:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public record Found&amp;lt;T&amp;gt;(T Value);

public record Empty();

public record ClientError(string Description);

public record ServerError(string Description);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other half of this is the base result class, which we can implement with the implicit casts off the bat:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public sealed class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    private DatabaseQueryResult&amp;lt;T&amp;gt;() { }

    public static implicit operator DatabaseQueryResult&amp;lt;T&amp;gt;(Found&amp;lt;T&amp;gt; found) =&amp;gt; new();

    public static implicit operator DatabaseQueryResult&amp;lt;T&amp;gt;(Empty empty) =&amp;gt; new();

    public static implicit operator DatabaseQueryResult&amp;lt;T&amp;gt;(ClientError clientError) =&amp;gt; new();

    public static implicit operator DatabaseQueryResult&amp;lt;T&amp;gt;(ServerError serverError) =&amp;gt; new();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the records which represent our result states already contain all of the information we need, so we don't need to duplicate those properties on the &lt;code&gt;DatabaseQueryResult&lt;/code&gt; object. In order to keep our state we can define an &lt;code&gt;object&lt;/code&gt; field for our result class to store whichever result state we've gotten. Since our result object is carefully guarding its instantiation and internal state, I don't think we need to worry about type safety here. If you've got a different situation you might need to implement something more robust, though.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public sealed class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    object _value;

    private DatabaseQueryResult&amp;lt;T&amp;gt;(object value) =&amp;gt;
        _value = value;

    // Implicit casts
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can use type checking to implement our &lt;code&gt;IsSuccess&lt;/code&gt; and &lt;code&gt;IsError&lt;/code&gt; methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public sealed class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    public bool IsSuccess([NotNullWhen(true)] out T? value)
    {
        if (_value is Found&amp;lt;T&amp;gt; found)
        {
            value = found.Value;
            return true;
        }

        _value = default;
        return false;
    }

    public bool IsError([NotNullWhen(true)] out string? description)
    {
        description = null;

        if (_value is ClientError clientError)
        {
            description = clientError.Description;
        }

        if (_value is ServerError serverError)
        {
            description = serverError.Description;
        }

        return description is null;
    }

    // Rest of implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could imagine that a consumer might care only about an empty state, and it's easy to implement an &lt;code&gt;IsEmpty&lt;/code&gt;, and this implementation is quite human-readable:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public bool IsEmpty() =&amp;gt;
    _value is Empty;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should give us everything we need for this nicer result type. Compare our previous implementation of &lt;code&gt;GetAllItemsFromDatabase&lt;/code&gt; to this new one:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt; GetAllItemsFromDatabase(...)
{
    try
    {
        var result = ContactDatabaseSomehow(...);

        if (result is IEnumerable&amp;lt;Item&amp;gt; items)
        {
            return new Found&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;(items);
        }
    }
    catch (/*exception indicating client error*/)
    {
        return new ClientError(exception.Message);
    }
    catch (/*exception indicating server error*/)
    {
        return new ServerError(exception.Message);
    }

    return new Empty();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I find myself very much in favor of this since &lt;code&gt;return new ServerError(exception.Message)&lt;/code&gt; makes a lot more senes to me than &lt;code&gt;return DatabaseQueryResult&amp;lt;IEnumerable&amp;lt;Item&amp;gt;&amp;gt;.ServerError(exception.Message)&lt;/code&gt;. It's not just shorter, it reads more clearly and in six months when I revisit my code I won't be scratching my head why this line needs to provide &lt;code&gt;IEnumerable&amp;lt;Item&amp;gt;&lt;/code&gt; as a type argument when it's got nothing to do with a list of items.&lt;/p&gt;
&lt;p&gt;This works great, but there's one more quality of life improvement we can give ourselves. When we consume one of these result objects, we'll need to consult either &lt;code&gt;IsSuccess&lt;/code&gt;, &lt;code&gt;IsError&lt;/code&gt;, or &lt;code&gt;IsEmpty&lt;/code&gt; - maybe two or all three of these - in order to direct our control flow, and this involves several conditional statements. Suppose we want to map this result onto a different value depending on its state - that will look even worse! The afrementioned &lt;code&gt;OneOf&lt;/code&gt; library contains a method for this purpose on the result object which accepts functions for each possible state to facilitate an easier way to achieve this mapping, though our custom implementation gives us the option of being able to name the arguments to this method in a way that match to the specific state of each, rather than the &lt;code&gt;t1&lt;/code&gt;, &lt;code&gt;t2&lt;/code&gt;, etc naming in that library.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public sealed class DatabaseQueryResult&amp;lt;T&amp;gt;
{
    public U Map&amp;lt;U&amp;gt;(
        Func&amp;lt;T, U&amp;gt; found,
        Func&amp;lt;U&amp;gt; empty,
        Func&amp;lt;string, U&amp;gt; error
    )
    {
        if (_value is Found&amp;lt;T&amp;gt; foundValue)
        {
            return found(foundValue.Value);
        }
        else if (_value is ClientError clientErrorValue)
        {
            return error(clientErrorValue.Description);
        }
        else if (_value is ServerError serverErrorValue)
        {
            return error(serverErrorValue.Description);
        }

        return empty();
    }
    
    public U Map&amp;lt;U&amp;gt;(
        Func&amp;lt;T, U&amp;gt; found,
        Func&amp;lt;U&amp;gt; empty,
        Func&amp;lt;string, U&amp;gt; clientError,
        Func&amp;lt;string, U&amp;gt; serverError
    )
    {
        if (_value is Found&amp;lt;T&amp;gt; foundValue)
        {
            return found(foundValue.Value);
        }
        else if (_value is ClientError clientErrorValue)
        {
            return clientError(clientErrorValue.Description);
        }
        else if (_value is ServerError serverErrorValue)
        {
            return serverError(serverErrorValue.Description);
        }

        return empty();
    }

    // Rest of implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two overloads for &lt;code&gt;Map&lt;/code&gt; allow our consumer to choose whether it cares about the client/server error distinction but still enforces that they must provide a complete mapping from any possible result state to an object. We could use this to rewrite our &lt;code&gt;ShowItems&lt;/code&gt; method from earlier in a clearer way:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public void ShowItems() =&amp;gt; Console.WriteLine(
    GetAllItemsFromDatabase(...)
    .Map(
        found: items =&amp;gt; string.Join(items.Select(i =&amp;gt; i.ToString()), &amp;quot;\n&amp;quot;),
        empty: () =&amp;gt; &amp;quot;No items found!&amp;quot;,
        error: description =&amp;gt; $&amp;quot;Error! {description}&amp;quot;
    )
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;em&gt;that's&lt;/em&gt; looking properly functional!&lt;/p&gt;
&lt;h1&gt;Future Approach: Discriminated Tag Unions&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Update: I added this section to give a little more context on my reference to discriminated unions in C# and the context in which this article exists.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I'm a big fan of these, and a big proponent of their inclusion in C#. The language does not currently support them, but the language design team is &lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md"&gt;actively discussing their inclusion&lt;/a&gt;. As I discussed earlier, our result is one of their use cases.&lt;/p&gt;
&lt;p&gt;Once DUs are included in the language, the above code will become obsolete. As nice as the API we're able to create is, it might still be a bit &lt;em&gt;interesting&lt;/em&gt; for a new member of your team discovering &lt;code&gt;.Map&lt;/code&gt; everywhere. It's not that it isn't usable or easy to code in, but it is different. Wouldn't it be nice to be able to use &lt;code&gt;switch&lt;/code&gt;? And on that point, wouldn't it be nicer to not need to include all the boilerplate?&lt;/p&gt;
&lt;p&gt;Indeed, at the end of the day while the solution I presented above is I think the best option currently, it is still hacky. I don't think this solution - or any others - will offer anything beyond what the language's own upcoming DU features will. The syntax for this feature is not set in stone, but &lt;a href="https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-27.md"&gt;a recent meeting and presentation&lt;/a&gt; by the language design team shows their current thinking, so I want to outline a snapshot of what the code that replaces my solution will look like.&lt;/p&gt;
&lt;p&gt;To start with, the boilerplate code that sets up the &lt;code&gt;DatabaseQueryResult&lt;/code&gt; and each of its options in 140 lines long, and while that isn't really a lot of code, it's certainly a lot of code given how simple the concept is. The syntax to create a union type may end up looking something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;union DatabaseQueryResult&amp;lt;T&amp;gt;
{
    Found(T Value);
    Empty();
    ClientError(string Description);
    ServerError(string Description);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;Map&lt;/code&gt; function necessary to boot, since the language will allow the use of &lt;code&gt;switch&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void ShowItems() =&amp;gt; Console.WriteLine(
    GetAllItemsFromDatabase(...) switch
    {
        Found(items) =&amp;gt; string.Join(items.Select(i =&amp;gt; i.ToString()), &amp;quot;\n&amp;quot;),
        Empty() =&amp;gt; &amp;quot;No items found!&amp;quot;,
        ClientError(description) or ServerError(description) =&amp;gt; $&amp;quot;Error! {description}&amp;quot;
    }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And ... that's it! From 164 lines total in my sample code down to 16.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;The complete code from this article &lt;a href="https://gist.github.com/IanWold/7efdf5df24bdd4e6ff5c652e1c051d6f"&gt;is available on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
      <pubDate>Fri, 03 May 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-05-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">scrum_is_not_agile</guid>
      <link>https://ian.wold.guru/Posts/scrum_is_not_agile.html</link>
      <title>Scrum is not Agile</title>
      <description>&lt;p&gt;One of my favorite lectures concerned with software engineering methodology is Allen Holub's &lt;a href="https://www.youtube.com/watch?v=F42A3R28WMU"&gt;War is Peace, Freedom is Slavery, Ignorance is Strength, Scrum is Agile&lt;/a&gt;, in which I believe he convincingly articulates that Scrum is harmful and that it is not equivalent to Agile. This lecture is particularly useful in drawing a clear distinction between Scrum and Agile - even if you do disagree with Holub's assertion that Scrum is not a proper implementation of Agile, you must agree that Scrum and Agile are &lt;em&gt;different&lt;/em&gt; things.&lt;/p&gt;
&lt;p&gt;The terms are thrown around interchangeably in our industry, and I think that does a disservice to both. I've heard a lot of criticisms levied against &amp;quot;Agile&amp;quot;, but it happens that the criticism is actually against specific practices in the Scrum guide, and while not as ubiquitous I've heard the opposite as well. If you disagree with the rest of my article here, I'd kindly suggest that you at least take this point away and encourage others to be a bit more precise when speaking in this area - neither criticism nor praise are useful if misattributed.&lt;/p&gt;
&lt;p&gt;Holub's thesis is stronger than just &amp;quot;Scrum and Agile are different things&amp;quot;, though, and it's the stronger point which I find convincing and which I want to defend here. That point is that Scrum is not an implementation of Agile. Holub means this as an indictment of Scrum, where he sees the Agile princiles as being, I think it's not unfair to say, the best set of principles we have right now to guide development methodology. I've &lt;a href="https://ian.wold.guru/Posts/book_club_12-2023.html"&gt;written about opinions on this&lt;/a&gt; before, so I'm not going to rehash &lt;em&gt;opinion&lt;/em&gt; on the matter here. Rather, I think I can defend a dispassionate form of this statement, that based on definitions Scrum can be said to not be an implementation of Agile, even though it claims to be.&lt;/p&gt;
&lt;p&gt;Agile is very broadly defined, as well it should be. Its purpose nowadays (I do not know the original intent) is to be a category of processes - there are processes which &lt;em&gt;are&lt;/em&gt; Agile and those which &lt;em&gt;are not&lt;/em&gt;, and helpfully there's going to end up being a murky middle ground with a fair amount of debate. Agile is bounded by a &lt;a href="https://agilemanifesto.org/"&gt;manifesto&lt;/a&gt; and &lt;a href="https://agilemanifesto.org/principles.html"&gt;twelve principles&lt;/a&gt;, making it obvious that any implementation of Agile needs to satisfy these properties. Is it necessary though for an implementation to &lt;em&gt;allow&lt;/em&gt; for these properties to be included with the specific methodology practiced, or is it necessary for an implementation to &lt;em&gt;support&lt;/em&gt; or even &lt;em&gt;require&lt;/em&gt; these properties? Take for example the following principle:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Certainly, if I invent a process which claims to implement Agile but in its definition precludes the ability for engineers to trust each other (say, one of its principles is &amp;quot;Trust nobody but yourself&amp;quot;), I can say that the process does not actually implement Agile. But is it necessary for an implementing system to specify that individuals within the system &lt;em&gt;must&lt;/em&gt; trust each other, or is the necessary component simply that the implementing system &lt;em&gt;allows&lt;/em&gt; for trust? Rather than splitting hairs, this is an important distinction which will affect how we interpret things, maybe drammatically. What do we do with the following Agile principle then, given this open question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Continuous attention to technical excellence and good design enhances agility.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This doesn't directly recommend a practice or category of practice unless we consider &amp;quot;enhancing agility&amp;quot; to be a good quality, but that doesn't necessarily emerge from the rest of the writing on the matter. As an aside, if you or a coworker are engaged in a definitional project as the authors of the Agile Manifesto were two decades ago, I can't recommend enough that you consult a philosopher, they have 2500 years of experience in this sort of work.&lt;/p&gt;
&lt;p&gt;Vaguely defined things like Agile can be broken down into &lt;em&gt;strong&lt;/em&gt; and &lt;em&gt;weak&lt;/em&gt; forms. A &lt;em&gt;strong Agile&lt;/em&gt; will be an implementation which makes clear and explicit accommodations for each of the Agile principles - trust in work would be encouraged or required, &amp;quot;enhancing agility&amp;quot; would be said to be good, and so on. A &lt;em&gt;weak Agile&lt;/em&gt; is one which might or might not explicitly require anything from the principles, but does not preclude or discourage any of them. This is a good distinction, but not terribly useful to us as it doesn't really give us a lot of teeth to be able to determine that something &lt;em&gt;is&lt;/em&gt; or &lt;em&gt;is not&lt;/em&gt; Agile, but rather a scale of Agility.&lt;/p&gt;
&lt;p&gt;We do actually see Agile and non-Agile things though, so we need some way of being able to draw a line in the sand. I'll propose that there are a smaller set of well-defined qualities which can be used to properly delineate Agile, and I think this can be derived from the manifesto and those principles which prescribe practice. Thus, I'll suggest that a system is not Agile unless it explicitly requires the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Software must be delivered in continuously iterations, its proper function for the user must be continuously monitored, and the improvement of the software for the requirements of the user must be the highest priority at all times;&lt;/li&gt;
&lt;li&gt;The stakeholders in the software all must be continuously engaged with by the engineers of a software, the engineers must be the sole owners of the software, and this relationship and working pace must be able to be carried on ad infinitum;&lt;/li&gt;
&lt;li&gt;The engineers must be the sole owners of their own specific processes, they must continuously evaluate and adapt these, and they must accommodate change in any scope at any point in time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the hard requirements which form the &amp;quot;core&amp;quot; of Agile; without specifically and explicitly adhering to these, a system is said to not be an implementation of Agile. While I've used three bullets for brevity, note that these are 9 points grouped into three.&lt;/p&gt;
&lt;p&gt;Defining Scrum is, helpfully, much easier, &lt;a href="https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-US.pdf"&gt;It has a very specific guide&lt;/a&gt; to reference. There are specific roles, practices, and structure defined. I'll use that document as a reference for the specific definition of Scrum.&lt;/p&gt;
&lt;p&gt;The question then is whether this document satisfies each of the above points - I'll go one-by-one.&lt;/p&gt;
&lt;p&gt;Scrum certainly requires the delivery of software in continuous iterations, a &lt;code&gt;sprint&lt;/code&gt; is defined:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They are fixed length events of one month or less to create consistency.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check. Is its function being continuously monitored? &lt;code&gt;Scrum artifacts&lt;/code&gt; certianly are:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Scrum artifacts and the progress toward agreed goals must be inspected frequently and diligently to detect potentially undesirable variances or problems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Though the document leaves it open whether these artifacts includes the software itself, I think that it's proper to grant a charitable reading to say so. As to whether properly-functioning software is elevated to the highest priority, it seems that a team's goals are rather the highest priority:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Scrum Team commits to achieving its goals and to supporting each other. Their primary focus is on the work of the Sprint to make the best possible progress toward these goals.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We would certainly hope that teams would, themselves, prioritize the functioning software, but this is not explicitly stated. As for the engineers being continuously engaged with the stakeholders, the Scrum guide falls short as well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Product Owner is one person, not a committee. The Product Owner may represent the needs of many stakeholders in the Product Backlog. Those wanting to change the Product Backlog can do so by trying to convince the Product Owner.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Being fair, this isolation can be beneficial in discrete applications, but to specify it as a part of the process goes against our second requirement for Agile. This Product Owner is also charged with developing the Product Goal, which is not explicitly defined. This is a problem as it ought at least be defined to exclude the possibility that the Product Owner might supplant the engineers as the sole owners of the software. Certainly nothing in the Scrum guide, I don't think, might be construed to suggest a working relationship or pace which is not infinitely maintainable.&lt;/p&gt;
&lt;p&gt;The Scrum guide fails again in keeping the engineers the owner of their own process, the most glaring way is the inclusion of the Scrum Master role - this is not a simple inclusion and wrestles process control, at least in part, away from the engineers. The Scrum guide is quite successful, by contrast, in mandating Review and Retrospective meetings, both of which are opportunities to evaluate and adapt the process. Accommodating change in any scope at any point in time is a bit more tricky though, take the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;During the Sprint: [...] No changes are made that would endanger the Sprint Goal;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Surely there's some way to change course though? Well, there is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Sprint could be cancelled if the Sprint Goal becomes obsolete. Only the Product Owner has the authority to cancel the Sprint.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don't take that inclusion to be accommodating of change though, these prescriptions might allow for some change but they place impediments in its way and refocus the attention away from change being in support of the software to change being antagonistic to the sprint goals.&lt;/p&gt;
&lt;p&gt;Is Scrum Agile? No, in the sense that they are just different things, but also no in the sense that Scrum is not an implementation of Agile. It's not just that there are required Agile prescriptions missing from Scrum, were that the case then I might be able to say that Scrum is an implementatino of a subset of Agile. I can't say that though because Scrum contradicts some Agile requirements. This all isn't to say anything negative or disparaging about Scrum, but rather to dispassionately say that it is not an implementation of Agile. Those committed to the whole of Scrum are, I think, committed to the position that not &lt;em&gt;all&lt;/em&gt; of Agile is desirable. So as to not throw the baby out with the bathwater, that might be alternatively phrased that &lt;em&gt;only some portion of Agile is desirable&lt;/em&gt;. In the same way, those who are committed to the whole of Agile are committed to the position that not &lt;em&gt;all&lt;/em&gt; of Scrum is desirable. Perhaps portions of it or ideas from it are.&lt;/p&gt;
</description>
      <pubDate>Wed, 05 Jun 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-06-05T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">shenanigans_as_an_accelerator</guid>
      <link>https://ian.wold.guru/Posts/shenanigans_as_an_accelerator.html</link>
      <title>Shenanigans as an Accelerator</title>
      <description>&lt;p&gt;If you're a fan of &lt;a href="https://www.nytimes.com/games/wordle/index.html"&gt;Wordle&lt;/a&gt;-ing with your work colleagues, you've probably run into a similar problem as I have. The NYTimes app has a feature to compete with friends on Wordle, but you'd (presumably) be signing into the NYTimes app with a personal account. Or, not everyone has the app, or a NYTimes account. Both of these are barriers to friendly office Wordling.&lt;/p&gt;
&lt;p&gt;My company uses Slack, which makes setting up workflows very easy. I presume Teams does as well. Crucially, Slack support two actions out of the box: it can paste form submissions into a Google Sheet, and it can set up an endpoint to ingest and parse any JSON string. On the Google side, their &lt;a href="https://workspace.google.com/intl/en_uk/products/apps-script/"&gt;Apps Scripts&lt;/a&gt; make interacting with Sheets quite easy, and allows sending HTTP requests. Creating a Wordle challenge Slack channel was all to obvious then: I have a form workflow in which colleagues paste Wordle results that get saved to the spreadsheet, then each morning at 10 an Apps Script posts the results and winners to the Slack channel and clears out that day's submissions.&lt;/p&gt;
&lt;p&gt;If you're already familiar with Slack and/or Apps Sctipts then that's nothing but obvious. Maybe a bit annoying to have to read spelled out. However, this ended up being different for me and my colleagues, as we sorted into a cohort defined by two facts: We did not know all that much about Slack workflows and Apps Scripts; and we have a number of scheduley-like things that could greatly benefit from such an automation.&lt;/p&gt;
&lt;p&gt;Setting up automations for common scheduley/reporting &lt;em&gt;things&lt;/em&gt; can be a huge accelerator for any team. Teams develop processes to make their work flow smoothly, and while those processes evolve over time they generally settle into a static state, with someone on the team tasked with upholding them. Where these processes involve maintaining lists, updating work items, or other manual interventions, would I rather that the process maintainer spend some hours per week performing this work themselves or some (much fewer) hours per year maintaining automations to resolve this work? As with any technological solution, poorly created or architected automations can end up requiring burdensome maintenance, but I want to focus here on the positive result of well-set-up, simple automations with common tools.&lt;/p&gt;
&lt;p&gt;What could be more common (for my firm) than Slack and Sheets, I do not know. I do know a couple other things though. For one, having ended up using Slack and Sheets to set up common automations, my team is much better off. One more important thing I know is that, many times, that work done for &lt;em&gt;work&lt;/em&gt; is rigid (maybe &lt;em&gt;rote&lt;/em&gt;), can tend towards over-engineering, but above all is typically non-exploratory. Maybe I'm getting a bit ahead of myself in my own blog post - how do those two relate?&lt;/p&gt;
&lt;p&gt;Well, it wasn't the case that we figured out how to set up the Wordle channel after having tasked someone with learning how to set up automations with Slack and Sheets. Rahter, it was the other way around: the automations we developed were a result of having set up the Wordle channel. In fact, we didn't even think of some of the automations we might do until possibilities were revealed to us by the Wordle channel. And, I assert this is the only direction we could have done this work - we could not have assigned out a task to develop the automations.&lt;/p&gt;
&lt;p&gt;Only by making something engaging we were able to stimulate the motivation to explore. Only by stepping outside - clearly outside - the boundaries of day-to-day work we were able to get a different perspective on problems, or what problems existed that needed solving. Easy solving, too.&lt;/p&gt;
&lt;p&gt;A Wordle competition Slack channel is surely some shenanigans, and thsoe shenanigans have a tangible, positive result on our work. Some might say that's work that was distracted from by play, but there's no denying the benefits realized. I'm resolving to shenanigan again, and I would encourage the same for you!&lt;/p&gt;
</description>
      <pubDate>Wed, 03 Sep 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-09-03T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">should_i_learn_insert_some_tech_here</guid>
      <link>https://ian.wold.guru/Posts/should_i_learn_insert_some_tech_here.html</link>
      <title>"Should I Learn (Insert Some Tech Here)?"</title>
      <description>&lt;p&gt;Yes.&lt;/p&gt;
</description>
      <pubDate>Tue, 14 Nov 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-11-14T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">sprache</guid>
      <link>https://ian.wold.guru/Posts/sprache.html</link>
      <title>An Introduction to Sprache</title>
      <description>&lt;p&gt;As my activity on this blog and my GitHub account may attest, I'm quite fond of a C# library called Sprache. Sprache is a parser-combinator that uses LINQ (Language INtegrated Query) to allow for the elegant construction of parsers in C#. I've been using Sprache for three years now, before I started college, and I've used it to implement a number of domain-specific languages (DSLs) both in side projects on my GitHub and on applications I've worked on. It's only natural I would want to share my favorite C# library with my fellow undergraduate classmates, but there are several factors which make it rather unapproachable for the average undergraduate computer science student. Thus, I have written this piece to provide a completely introductory tutorial to using Sprache.&lt;/p&gt;
&lt;p&gt;I'll explain LINQ and BNF, and then I'll walk you through the implementation of a few simple grammars in Sprache such that I may touch upon all the most important concepts in the Sprache library to allow the reader to immediately begin to implement the grammars which they desire. At the end of this post, I link to several articles which cover the framework and other related readings. In the future, I may also write a short handbook/reference to certain Sprache concepts.&lt;/p&gt;
&lt;p&gt;One does not necessarily need to have an understanding of C# to begin using Sprache, but a familiarity of a similar language (i.e. Java) would go a long way. I'm going to assume the reader has an understanding of object-oriented programming. I won't be going into an explanation of what a parser-combinator is, nor what a &amp;quot;combinator&amp;quot; is, in general. If you would like to become more involved in the development of Sprache, though, you should definitely familiarize yourself with the concept. I provide some links at the end of this tutorial to that end.&lt;/p&gt;
&lt;h1&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;To begin with, of course, you'll need to download Sprache. You can find it &lt;a href="https://github.com/sprache/Sprache"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;LINQ&lt;/h2&gt;
&lt;p&gt;LINQ, short for Language INtegrated Query, is a wonderful feature of Visual C# which adds data-querying operators to C#. LINQ expressions are sometimes (grammatically incorrectly) referred to as &amp;quot;LINQ queries&amp;quot; as they read rather fluently as a query on a data set. Here is an example of a LINQ expression:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;var myList = new List&amp;lt;string&amp;gt;()
{
    &amp;quot;hello&amp;quot;,
    &amp;quot;world&amp;quot;,
    &amp;quot;how&amp;quot;,
    &amp;quot;are&amp;quot;,
    &amp;quot;you&amp;quot;
};

var startsWithH =
    from s in myList
    where s.ToCharArray()[0] == 'h'
    select s;

foreach (var a in startsWithH)
	Console.WriteLine(a);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we start with a list of words, and we desire to print to the console each word which begins with the letter 'h'. The variable &lt;em&gt;startsWithH&lt;/em&gt; is defined with the following LINQ expression, which is how we sort out those words which start with 'h':&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;from s in myList
where s.ToLower().ToCharArray()[0] == 'h'
select s;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's look at what's going on here. First, we have a &lt;em&gt;from&lt;/em&gt; statement. This will iterate over each object in &lt;em&gt;myList&lt;/em&gt;, using &lt;em&gt;s&lt;/em&gt; as the iterator variable. Next, we have a &lt;em&gt;where&lt;/em&gt; statement, which filters out the objects in &lt;em&gt;myList&lt;/em&gt; based on the condition provided. Note that several &lt;em&gt;where&lt;/em&gt; statements could be specified here. At the end of this LINQ expression, as with every LINQ expression, we have a &lt;em&gt;select&lt;/em&gt; statement, which returns each &amp;quot;queried&amp;quot; object. In this case, we only desire to return the strings which begin with the letter 'h'.&lt;/p&gt;
&lt;p&gt;LINQ supports several operators apart from &lt;em&gt;from&lt;/em&gt;, &lt;em&gt;where&lt;/em&gt;, and &lt;em&gt;select&lt;/em&gt;, though these are the main ones. Microsoft, naturally, provides a very in-depth &lt;a href="https://msdn.microsoft.com/en-us/library/bb394939.aspx"&gt;list of LINQ operators&lt;/a&gt;, though Wikipedia has &lt;a href="https://en.wikipedia.org/wiki/Language_Integrated_Query#Standard_Query_Operators"&gt;a much more succinct list&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sprache uses LINQ to construct its parsers. This allows for quick implementation and easy and intuitive readability.&lt;/p&gt;
&lt;h2&gt;Backus-Naur Form&lt;/h2&gt;
&lt;p&gt;Backus-Naur Form, or BNF for short, is a metalanguage used to describe the grammars and syntax of context-free grammars (essentially, for our purposes, this means the grammars of computing languages). BNF defines expressions in terms of other expressions and strings using a number of rules which will become more familiar as we begin implementing these grammars in Sprache.&lt;/p&gt;
&lt;p&gt;As an example, suppose I want to define a grammar which specifies an arithmetic expression which might add, subtract, multiply, or divide two digits. I'll provide a BNF definition of this grammar, and then explain it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;expr&amp;gt;      ::= &amp;lt;add&amp;gt; | &amp;lt;subtract&amp;gt; | &amp;lt;multiply&amp;gt; | &amp;lt;divide&amp;gt;

&amp;lt;add&amp;gt;       ::= &amp;lt;digit&amp;gt; &amp;quot;+&amp;quot; &amp;lt;digit&amp;gt;
&amp;lt;subtract&amp;gt;  ::= &amp;lt;digit&amp;gt; &amp;quot;-&amp;quot; &amp;lt;digit&amp;gt;
&amp;lt;multiply&amp;gt;  ::= &amp;lt;digit&amp;gt; &amp;quot;*&amp;quot; &amp;lt;digit&amp;gt;
&amp;lt;divide&amp;gt;    ::= &amp;lt;digit&amp;gt; &amp;quot;/&amp;quot; &amp;lt;digit&amp;gt;

&amp;lt;digit&amp;gt;     ::= &amp;quot;0&amp;quot; | &amp;quot;1&amp;quot; | &amp;quot;2&amp;quot; | &amp;quot;3&amp;quot; | &amp;quot;4&amp;quot; | &amp;quot;5&amp;quot; | &amp;quot;6&amp;quot; | &amp;quot;7&amp;quot; | &amp;quot;8&amp;quot; | &amp;quot;9&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's look at each of the elements and what they do. First, the most notable and important element is the reference for an expression, which looks like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;expression_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The expressions are referenced by this convention, and they are defined with the &lt;em&gt;::=&lt;/em&gt; operator. In defining such expressions, a number of rules can be used. Look at the definition for &lt;em&gt;expr&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;expr&amp;gt;      ::= &amp;lt;add&amp;gt; | &amp;lt;subtract&amp;gt; | &amp;lt;multiply&amp;gt; | &amp;lt;divide&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bar ('|') denotes an &lt;em&gt;or&lt;/em&gt; relationship. That is, an expression &lt;em&gt;expr&lt;/em&gt; can be either an &lt;em&gt;add&lt;/em&gt;, &lt;em&gt;subtract&lt;/em&gt;, &lt;em&gt;multiply&lt;/em&gt;, or &lt;em&gt;divide&lt;/em&gt; expression.&lt;/p&gt;
&lt;p&gt;Now let's look at the definition of the &lt;em&gt;add&lt;/em&gt; expression:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;add&amp;gt;       ::= &amp;lt;digit&amp;gt; &amp;quot;+&amp;quot; &amp;lt;digit&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This specifies that an &lt;em&gt;add&lt;/em&gt; can be a &lt;em&gt;digit&lt;/em&gt;, followed by a plus sign, followed by another &lt;em&gt;digit&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You might notice a bit of an inefficiency in the grammar I defined above. Namely, we define &lt;em&gt;add&lt;/em&gt;, &lt;em&gt;subtract&lt;/em&gt;, &lt;em&gt;multiply&lt;/em&gt;, and &lt;em&gt;divide&lt;/em&gt; separately, but due to the similarity in their structures, it feels like we should be able to define them all together. While there are certainly good reasons one might want to define them separately as I did above, for succinctness one might desire to redefine the grammar as such:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;expr&amp;gt;      ::= &amp;lt;digit&amp;gt; (&amp;quot;+&amp;quot; | &amp;quot;-&amp;quot; | &amp;quot;*&amp;quot; | &amp;quot;/&amp;quot;) &amp;lt;digit&amp;gt;

&amp;lt;digit&amp;gt;     ::= &amp;quot;0&amp;quot; | &amp;quot;1&amp;quot; | &amp;quot;2&amp;quot; | &amp;quot;3&amp;quot; | &amp;quot;4&amp;quot; | &amp;quot;5&amp;quot; | &amp;quot;6&amp;quot; | &amp;quot;7&amp;quot; | &amp;quot;8&amp;quot; | &amp;quot;9&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I introduce a grouping of terms, defined by the parentheses. Now, &lt;em&gt;expr&lt;/em&gt; is defined to be two &lt;em&gt;digits&lt;/em&gt; separated by either an addition, subtraction, multiplication, or division symbol. This is, however, a rather dumb grammar, in that only two &lt;em&gt;digits&lt;/em&gt; can be used in the arithmetic expression, while we might want to allow any number to be used. We can extend the grammar further to allow for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;expr&amp;gt;      ::= &amp;lt;number&amp;gt; (&amp;quot;+&amp;quot; | &amp;quot;-&amp;quot; | &amp;quot;*&amp;quot; | &amp;quot;/&amp;quot;) &amp;lt;number&amp;gt;

&amp;lt;number&amp;gt;    ::= &amp;lt;integer&amp;gt; [&amp;quot;.&amp;quot; &amp;lt;integer&amp;gt;]
&amp;lt;integer&amp;gt;   ::= +(&amp;quot;0&amp;quot; | &amp;quot;1&amp;quot; | &amp;quot;2&amp;quot; | &amp;quot;3&amp;quot; | &amp;quot;4&amp;quot; | &amp;quot;5&amp;quot; | &amp;quot;6&amp;quot; | &amp;quot;7&amp;quot; | &amp;quot;8&amp;quot; | &amp;quot;9&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have broken a number into two parts, a &lt;em&gt;number&lt;/em&gt; and an &lt;em&gt;integer&lt;/em&gt;. Where I define &lt;em&gt;integer&lt;/em&gt;, I introduce a plus sign, which allows the expression which it suffixes to be repeated one or more times. Where I define &lt;em&gt;number&lt;/em&gt;, I introduce the square brackets, which surround optional expressions. Thus, the following terms are captured by the expression &lt;em&gt;number&lt;/em&gt;: 0, 125, 3.14, and 123.456. However, the following terms are not captured by &lt;em&gt;number&lt;/em&gt;, and I will allow the reader to postulate why they are not, and how the grammar might need to be altered to capture them: .31, -12, -12.56, and -.987.&lt;/p&gt;
&lt;p&gt;When we want to parse a language with Sprache (or any other parser, for that matter), we will first define the language in BNF, so that we can easily reference the pieces of the parser we must create, and to keep track of our progress.&lt;/p&gt;
&lt;h1&gt;Sprache&lt;/h1&gt;
&lt;p&gt;Ultimately, once you get used to using LINQ to construct parsers, Sprache is just another library, and becoming proficient in Sprache is the same process one should be used to of learning the methods given by the library and learning how to ask questions on stack overflow.&lt;/p&gt;
&lt;p&gt;Let's begin familiarizing ourselves by constructing a parser which can parse the string &amp;quot;hello&amp;quot; into a string &amp;quot;hello.&amp;quot; This is an entirely non-useful task for Sprache, but it gets our feet wet:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;string&amp;gt; myParser =
    from str in Parse.String(&amp;quot;hello&amp;quot;).Text()
    select str;

string val = myParser.Parse(&amp;quot;hello&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;val&lt;/em&gt; will, unremarkably, be &amp;quot;hello.&amp;quot;However, the parser should be very easy to understand, especially given our understanding of the working of a LINQ expression. The method &lt;em&gt;String(string)&lt;/em&gt; is a parser which parses any string you desire (in this case, we desired to parse the string &amp;quot;hello&amp;quot;). The &lt;em&gt;String&lt;/em&gt; parser returns an enumerable of chars, so we need to use &lt;em&gt;Text&lt;/em&gt; to turn the enumerable into a string. From there, it should be rather obvious what is going on.&lt;/p&gt;
&lt;p&gt;Now, let's suppose we want to parse the string &amp;quot;hello&amp;quot; multiple times, separated by whitespace, and we want to know how many times &amp;quot;hello&amp;quot; appears. We can extend our parser above like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;int&amp;gt; myParser =
    from str in
        Parse.String(&amp;quot;hello&amp;quot;).Text()
        .DelimitedBy(Parse.WhiteSpace.Many())
    select str.Count();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing this parser with the string &amp;quot;hello   hellohello  hello&amp;quot; should return a result of 4. Because of the way our parser is constructed, it is relatively straightforward to read it as &amp;quot;parse the string &amp;quot;hello&amp;quot; delimited by whitespace.&amp;quot; But let's look at what's going on here. &lt;em&gt;DelimitedBy&lt;/em&gt; will attempt to match the &amp;quot;hello&amp;quot; parser, and then it will look for whitespace (&lt;em&gt;WhiteSpace().Many()&lt;/em&gt; is a parser itself which matches 0 or more different whitespace characters in a row), and will then look to match &amp;quot;hello&amp;quot; again and more whitespace, until the parser is no longer able to match either &amp;quot;hello&amp;quot; or whitespace, at which point it returns an &lt;em&gt;IEnumberable&lt;/em&gt; containing several &amp;quot;hello&amp;quot;s. Our &lt;em&gt;select&lt;/em&gt; statement can then select the &lt;em&gt;Count&lt;/em&gt; of that &lt;em&gt;IEnumerable&lt;/em&gt;, and thus we can obtain the number of times &amp;quot;hello&amp;quot; is parsed.&lt;/p&gt;
&lt;p&gt;This idea of chaining parsers onto each other, as &lt;em&gt;DelimitedBy&lt;/em&gt; is chained onto &lt;em&gt;String&lt;/em&gt;, is the entire concept behind parser-combinators.&lt;/p&gt;
&lt;p&gt;A slightly more complicated task might be to try to parse a variable name surrounded by whitespace (often called an &amp;quot;identifier&amp;quot;). This example is given on the Sprache GitHub page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;string&amp;gt; identifier =
    from leading in Parse.WhiteSpace.Many()
    from first in Parse.Letter.Once()
    from rest in Parse.LetterOrDigit.Many()
    from trailing in Parse.WhiteSpace.Many()
    select new string(first.Concat(rest).ToArray());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, &amp;quot;   abc123   &amp;quot; should come out as &amp;quot;abc123&amp;quot;. Notice how staggering several &lt;em&gt;from&lt;/em&gt; statements in a row reads as though we are saying &amp;quot;then&amp;quot;. For example, this parser could be read by a human as &amp;quot;Parse many whitespace characters, &lt;em&gt;then&lt;/em&gt; parse one letter, &lt;em&gt;then&lt;/em&gt; parse 0 or more letters or digits, &lt;em&gt;then&lt;/em&gt; parse more whitespace, and return the first letter and the rest of the letters/digits concatenated to it&amp;quot;.&lt;/p&gt;
&lt;h2&gt;Our First Language&lt;/h2&gt;
&lt;p&gt;So, let's now define the grammar for a small DSL, and we'll try to parse it. Let's make a language that defines variables: we can have an identifier, followed by a colon, and then a string, and we can define as many variables as we want on different lines. Ultimately, we want to parse this into a dictionary. So, our resulting language could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;identifier1 : &amp;quot;hello&amp;quot;
identifier2 : &amp;quot;world&amp;quot;
identifier3 : &amp;quot;yay parsing&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The BNF for the language looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-BNF"&gt;&amp;lt;block&amp;gt;        ::= &amp;lt;expr&amp;gt; *(&amp;lt;newline&amp;gt; &amp;lt;expr&amp;gt;)
&amp;lt;expr&amp;gt;         ::= &amp;lt;identifier&amp;gt; [&amp;lt;whitespace&amp;gt;] &amp;quot;:&amp;quot; [&amp;lt;whitespace&amp;gt;] &amp;lt;string&amp;gt;
&amp;lt;identifier&amp;gt;   ::= &amp;lt;letter&amp;gt; *(&amp;lt;letter&amp;gt; | &amp;lt;digit&amp;gt;)
&amp;lt;string&amp;gt;       ::= &amp;quot;\&amp;quot;&amp;quot; *(&amp;lt;any_character&amp;gt;) &amp;quot;\&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'll imagine you can determine what &lt;em&gt;newline&lt;/em&gt;, &lt;em&gt;letter&lt;/em&gt;, &lt;em&gt;digit&lt;/em&gt;, and &lt;em&gt;any_character&lt;/em&gt; are. Note, though, that we technically want &lt;em&gt;any_character&lt;/em&gt; to parse any character except a quotation mark.&lt;/p&gt;
&lt;p&gt;Sprache already contains parsers for a letter, digit, and any character, so we should be all good to go from here. We already have our identifier parser, so let's add to that by constructing our string parser:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;string&amp;gt; identifier =
    from first in Parse.Letter.Once()
    from rest in Parse.LetterOrDigit.Many()
    select new string(first.Concat(rest).ToArray());

Parser&amp;lt;string&amp;gt; stringParser =
    from first in Parse.Char('&amp;quot;')
    from text in Parse.AnyChar.Except(Parse.Char('&amp;quot;')).Many().Text()
    from last in Parse.Char('&amp;quot;')
    select text;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we use &lt;em&gt;Except&lt;/em&gt; to add an exception to the &lt;em&gt;AnyChar&lt;/em&gt; parser. In addition, we use &lt;em&gt;Text&lt;/em&gt; at the end to tell sprache to convert the IEnumerable returned by &lt;em&gt;Many&lt;/em&gt; into a string. Now, we can add the parser for the expressions:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;//Adding to the code above:

Parser&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt; expr =
    from id in identifier
    from colon in Parse.Char(':').Token()
    from str in stringParser
    select new Dictionary&amp;lt;string, string&amp;gt;() { { id, str } };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Optional&lt;/em&gt; is used here - that does exactly what it says - it makes the parser optional. In addition, I introduced &lt;em&gt;Token&lt;/em&gt;, which will parse whitespace before and after the &lt;em&gt;CHar&lt;/em&gt; parser. Notice how we are able to reference the parsers we created earlier, and we can use the values they return to create a new object. Let's finish it off by creating the &amp;quot;block&amp;quot; parser, which is supposed to parse several expressions separated by newlines:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;//Adding to the code above:

Parser&amp;lt;IEnumerable&amp;lt;Dictionary&amp;lt;string,string&amp;gt;&amp;gt;&amp;gt; block =
    expr.DelimitedBy(Parse.Char('\n'));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice  how we are not using the fancy LINQ expressions here. Because our parser fits on one line, and &lt;em&gt;DelimitedBy&lt;/em&gt; returns the type that we want, then we can condense our parser a bit. Now that we're done with our parser, we should be able to parse our example file just fine into an &lt;em&gt;IEnumberable&lt;/em&gt; of dictionaries containing our identifier-string pairs.&lt;/p&gt;
&lt;h2&gt;Comma-Separated Values&lt;/h2&gt;
&lt;p&gt;CSV files are extremely popular for storing tables in plaintext, and they're very easy to parse, as you might imagine. Frequently, programs which read from CSV files desire to read the files into their own data structures. So, we'll imagine a couple different scenarios involving CSVs, and we'll look at how we can go about parsing each one.&lt;/p&gt;
&lt;p&gt;First, I'll provide a rough CSV parser that sorts the CSV into a list of a list of strings, and from there we can talk about a custom data structure for it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;IEnumerable&amp;lt;IEnumerable&amp;lt;string&amp;gt;&amp;gt;&amp;gt; csv = 
    Parse.AnyChar.Except(Parse.Char(',')
        .Or(Parse.Char('\n'))).Many().Text()
        .DelimitedBy(Parse.Char(',').Token())
        .DelimitedBy(Parse.Char('\n'));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This parser is quite fun, as it can be written in one line, yet it parses a CSV file pretty much alright - you might notice that none of the values can contain a comma (Bonus problem: see if you can get the parser to recognize escape characters so that the user can insert commas. Later, in the JSON parser, we'll implement escape characters).&lt;/p&gt;
&lt;p&gt;Let's digress now, and suppose we have the following simplistic data structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;class Row
{
    public string Title { get; set; }
    public IEnumerable&amp;lt;string&amp;gt; Items { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And let's further suppose that in a CSV, the first item of every row is the title of that row, and the remaining elements in that row are the items, corresponding to the structure above. So, we want to get a list of these rows, presumably. We can very easily modify our parser:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;Row&amp;gt; line =
    from first in
        Parse.AnyChar
        .Except(Parse.Char(',')
        .Or(Parse.Char('\n')))
        .Many().Text()
    from comma in Parse.Char(',').Token()
    from rest in
        Parse.AnyChar
        .Except(Parse.Char(',')
        .Or(Parse.Char('\n'))).Many().Text()
        .DelimitedBy(Parse.Char(',').Token())
    select new Row() { Title = first, Items = rest };

Parser&amp;lt;IEnumerable&amp;lt;Row&amp;gt;&amp;gt; csv = line.DelimitedBy(Parse.Char('\n'));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Naturally, if you're just interested in obtaining the rows, then this parser works perfectly. But let's suppose we didn't want the nested lists to contain the rows, but the columns. In this case, we can do some nifty snafu:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;IEnumerable&amp;lt;string&amp;gt;&amp;gt; line =
    Parse.AnyChar.Except(Parse.Char(',')
        .Or(Parse.Char('\n'))).Many().Text()
        .DelimitedBy(Parse.Char(',').Token());

Parser&amp;lt;IEnumerable&amp;lt;IEnumerable&amp;lt;string&amp;gt;&amp;gt;&amp;gt; csv =
    from l in line.DelimitedBy(Parse.Char('\n'))
    select Transform(l);

//Here's the Transform method:
//Assume the table is n-by-n
static IEnumerable&amp;lt;IEnumerable&amp;lt;string&amp;gt;&amp;gt; Transform(IEnumerable&amp;lt;IEnumerable&amp;lt;string&amp;gt;&amp;gt; t)
{
    var toReturn = new List&amp;lt;List&amp;lt;string&amp;gt;&amp;gt;();
    
    for (int i = 0; i &amp;lt; t.ElementAt(0).Count(); i++)
    {
        for (int j = 0; j &amp;lt; t.Count(); j++)
        {
            if (toReturn.Count == i) toReturn.Add(new List&amp;lt;string&amp;gt;);
            if (toReturn[i].Count == j) toReturn[i].Add(&amp;quot;&amp;quot;);
            
            toReturn[i][j] = t.ElementAt(j).ElementAt(i);
        }
    }

    return toReturn;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Transform&lt;/em&gt; just rotates the list of lists as though it's a matrix, so we're not adding anything too special here. What if we wanted to do what we did above with the rows, but with the columns? Try modifying this code to do just that. Bonus points if you can eliminate &lt;em&gt;Transform&lt;/em&gt; and perform the transformation within the parser!&lt;/p&gt;
&lt;h2&gt;XML&lt;/h2&gt;
&lt;p&gt;Obviously, XML is a rather complex language, and a &lt;a href="http://www.w3.org/TR/REC-xml/"&gt;complete BNF specification&lt;/a&gt; is thus very large. Therefore, we'll be using a much simpler variation of XML, which we can see below:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;tag&amp;gt; ::= &amp;lt;single_line_tag&amp;gt; | &amp;lt;multi_line_tag&amp;gt;
&amp;lt;short_tag&amp;gt; ::= &amp;quot;&amp;lt;&amp;quot; &amp;lt;identifier&amp;gt; &amp;lt;whitespace&amp;gt; &amp;lt;attribute&amp;gt;* &amp;quot;/&amp;gt;&amp;quot;
&amp;lt;full_tag&amp;gt; ::= &amp;quot;&amp;lt;&amp;quot; &amp;lt;identifier&amp;gt; &amp;lt;whitespace&amp;gt; &amp;lt;attribute&amp;gt;* &amp;quot;&amp;gt;&amp;quot; &amp;lt;tag&amp;gt;*
    &amp;quot;&amp;lt;/&amp;quot; &amp;lt;identifier&amp;gt; &amp;quot;&amp;gt;&amp;quot;
&amp;lt;attribute&amp;gt; ::= &amp;lt;identifier&amp;gt; &amp;quot;=&amp;quot; &amp;quot;\&amp;quot;&amp;quot; &amp;lt;any_characters&amp;gt; &amp;quot;\&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might notice I'm leaving out a few unnecessary components: Expressions which are intuitively obvious aren't defined, &lt;em&gt;whitespace&lt;/em&gt; is only used where necessary (we'll use &lt;em&gt;Token&lt;/em&gt; prolifically to allow for flexibility on the user's part), and when defining a &amp;quot;full&amp;quot; tag, the identifier of the opening and closing tags must be the same. The latter component cannot be defined in vanilla BNF, so it's something we'll need to account for in our code.&lt;/p&gt;
&lt;p&gt;Instead of writing this code out, I'll reference the &lt;a href="https://github.com/sprache/Sprache/blob/master/src/XmlExample/Program.cs"&gt;XML example&lt;/a&gt; which is included with Sprache, and I'll explain the new elements and solutions found there.&lt;/p&gt;
&lt;p&gt;Right off the bat, looking at the &lt;em&gt;Document&lt;/em&gt; parser, we're introduced to a new use of LINQ. As I alluded to previously, any LINQ query can be written in one line without the LINQ statements. Here's the critical portion of line 98:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Node.Select(n =&amp;gt; new Document { Root = n })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make this easier to comprehend, we can write it out using the regular LINQ notation we're familiar with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;from n in Node
select new Document() { Root = n };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In fact, the LINQ statements we've been are just a shorthand (or perhaps more of a &amp;quot;paraphrasing,&amp;quot; as they tend to be longer) for the inline notation. &lt;em&gt;Document&lt;/em&gt;
very well could have been written using solely the LINQ statements, and that would have looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;public static readonly Parser&amp;lt;Document&amp;gt; Document =
    from leading in Parse.WhiteSpace.Many()
    from doc in from n in Node.End()
                select new Document() { Root = n }
    select doc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next parser up from &lt;em&gt;Document&lt;/em&gt; is &lt;em&gt;Item&lt;/em&gt;. We'll ignore the code regarding the comments (if you're interested in this, please see my post &lt;a href="https://ianwold.silvrback.com/parsing-comments-with-sprache"&gt;Parsing Comments with Sprache&lt;/a&gt;). This makes it easy to see that an &lt;em&gt;Item&lt;/em&gt; is either a &lt;em&gt;Node&lt;/em&gt; cast as an Item, or a &lt;em&gt;Content&lt;/em&gt;, and a &lt;em&gt;Node&lt;/em&gt; (looking above in the document) is either a short or full node.&lt;/p&gt;
&lt;p&gt;Looking at the &lt;em&gt;ShortNode&lt;/em&gt; parser, we can see it seems completely familiar, except that the LINQ expression is used as an argument to the &lt;em&gt;Tag&lt;/em&gt; method. The &lt;em&gt;Tag&lt;/em&gt; method returns a parser that parses a greater than and less than sign before and after the parser you specify. This abstraction allows us to write cleaner code.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;FullNode&lt;/em&gt; is fun for a couple reasons. First, look at how they solved the issue of requiring opening and closing tags to be named the same with the &lt;em&gt;EndTag&lt;/em&gt; method. In addition, notice the use of the &lt;em&gt;Ref&lt;/em&gt; parser. In &lt;em&gt;FullNode&lt;/em&gt;, we need to use &lt;em&gt;Item&lt;/em&gt;, but it has obviously not yet been created. &lt;em&gt;Ref&lt;/em&gt; allows us to reference a parser later in the document, thus allowing us to create some recursive or ambiguous grammars with Sprache.&lt;/p&gt;
&lt;h2&gt;JSON&lt;/h2&gt;
&lt;p&gt;JSON, or JavaScript Object Notation, is kind of like XML. It's a way of storing data in plaintext (in a key-value pair manner) which is also easily readable by a human. In addition, it's very easy to construct a parser for it. The BNF form is very clear and concise - here, I have transcribed the &lt;a href="http://json.org/"&gt;informal definition on json.org&lt;/a&gt; into the more formal BNF notation which we have been using in this tutorial:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;object&amp;gt;    ::= &amp;quot;{}&amp;quot; | &amp;quot;{&amp;quot; &amp;lt;members&amp;gt; &amp;quot;}&amp;quot;
&amp;lt;members&amp;gt;   ::= &amp;lt;pair&amp;gt; | &amp;lt;pair&amp;gt; &amp;quot;,&amp;quot; &amp;lt;members&amp;gt;
&amp;lt;pair&amp;gt;      ::= &amp;lt;string&amp;gt; &amp;quot;:&amp;quot; &amp;lt;value&amp;gt;
&amp;lt;array&amp;gt;     ::= &amp;quot;[]&amp;quot; | &amp;quot;[&amp;quot; &amp;lt;elements&amp;gt; &amp;quot;]&amp;quot;
&amp;lt;elements&amp;gt;  ::= &amp;lt;value&amp;gt; | &amp;lt;value&amp;gt; &amp;quot;,&amp;quot; &amp;lt;elements&amp;gt;
&amp;lt;value&amp;gt;     ::= &amp;lt;literal&amp;gt; | &amp;lt;array&amp;gt; | &amp;lt;object&amp;gt;
&amp;lt;literal&amp;gt;   ::= &amp;lt;string&amp;gt; | &amp;lt;number&amp;gt; | &amp;lt;bool&amp;gt; | &amp;quot;null&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of a valid JSON file might be the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;firstName&amp;quot;: &amp;quot;John&amp;quot;,
  &amp;quot;lastName&amp;quot;: &amp;quot;Smith&amp;quot;,
  &amp;quot;age&amp;quot;: 25,
  &amp;quot;address&amp;quot;: {
    &amp;quot;streetAddress&amp;quot;: &amp;quot;21 2nd Street&amp;quot;,
    &amp;quot;city&amp;quot;: &amp;quot;New York&amp;quot;,
    &amp;quot;state&amp;quot;: &amp;quot;NY&amp;quot;,
    &amp;quot;postalCode&amp;quot;: &amp;quot;10021-3100&amp;quot;
  },
  &amp;quot;phoneNumbers&amp;quot;: [
    {
      &amp;quot;type&amp;quot;: &amp;quot;home&amp;quot;,
      &amp;quot;number&amp;quot;: &amp;quot;212 555-1234&amp;quot;
    },
    {
      &amp;quot;type&amp;quot;: &amp;quot;office&amp;quot;,
      &amp;quot;number&amp;quot;: &amp;quot;646 555-4567&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should allow us to construct the data structure we're going to store our data in (called an Abstract Syntax Tree):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;
public class JSONValue {}

public class JSONObject : JSONValue
{
    public Dictionary&amp;lt;string, JSONValue&amp;gt; Pairs { get; set; }

    public JSONObject(IEnumerable&amp;lt;KeyValuePair&amp;lt;string, JSONValue&amp;gt;&amp;gt; pairs)
    {
        Pairs = new Dictionary&amp;lt;string, JSONValue&amp;gt;();
        if (pairs != null)
            foreach (var p in pairs)
                Pairs.Add(p.Key, p.Value);
    }
}

public class JSONArray : JSONValue
{
    public List&amp;lt;JSONValue&amp;gt; Elements { get; set; }

    public JSONArray(IEnumerable&amp;lt;JSONValue&amp;gt; elements)
    {
        Elements = new List&amp;lt;JSONValue&amp;gt;();
        if (elements != null)
            foreach (var e in elements)
                Elements.Add(e);
    }
}

public class JSONLiteral : JSONValue
{
    public string Value { get; set; }

    public LiteralType ValueType { get; set; }

    public JSONLiteral(string value, LiteralType type)
    {
        Value = value;
        ValueType = type;
    }

    pubilc static enum LiteralType
    {
        String,
        Number,
        Boolean,
        Null
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To implement the parser, we'll start from the bottom and work our way up, as we usually do. Literal values are expressed nicely by our JSONLiteral class, which stores every value as a string, and also keeps track of the type of literal it is. Parsing them all out is a bit of a pain, so I'll post each parser here and explain it briefly.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;JSONLiteral&amp;gt; JNull =
    from str in Parse.IgnoreCase(&amp;quot;null&amp;quot;)
    select new JSONLiteral(null, JSONLiteral.LiteralType.Null);

Parser&amp;lt;JSONLiteral&amp;gt; JBoolean =
    from str in Parse.IgnoreCase(&amp;quot;true&amp;quot;).Text()
    			.Or(Parse.IgnoreCase(&amp;quot;false&amp;quot;).Text())
    select new JSONLiteral(str, LiteralType.Boolean);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parsing a literal null or boolean value isn't all too complicated. We just need to parse the strings which represent them, ignoring the case, and return new JSONLiteral objects.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;string&amp;gt; JExp =
    from e in Parse.IgnoreCase(&amp;quot;e&amp;quot;).Text()
    from sign in Parse.String(&amp;quot;+&amp;quot;).Text()
                 .Or(Parse.String(&amp;quot;-&amp;quot;).Text())
                 .Optional()
    from digits in Parse.Digit.Many().Text()
    select e + ((sign.IsDefined) ? sign.Get() : &amp;quot;&amp;quot;) + digits;

Parser&amp;lt;string&amp;gt; JFrac =
    from dot in Parse.String(&amp;quot;.&amp;quot;).Text()
    from digits in Parse.Digit.Many().Text()
    select dot + digits;

Parser&amp;lt;string&amp;gt; JInt =
    from minus in Parse.String(&amp;quot;-&amp;quot;).Text().Optional()
    from digits in Parse.Digit.Many().Text()
    select (minus.IsDefined ? minus.Get() : &amp;quot;&amp;quot;) + digits;

Parser&amp;lt;JSONLiteral&amp;gt; JNumber =
    from integer in JInt
    from frac in JFrac.Optional()
    from exp in JExp.Optional()
    select new JSONLiteral(integer +
                           (frac.IsDefined ? frac.Get() : &amp;quot;&amp;quot;) +
                           (exp.IsDefined ? exp.Get() : &amp;quot;&amp;quot;),
                           LiteralType.Number);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parsing a number is much more exciting. We need to account for integers, decimals, negation, and 'e'. The code above for &lt;em&gt;JNumber&lt;/em&gt; knows we need at least an integer, and can be optionally followed by the fraction or the exponential term. Notice that &lt;em&gt;Optional&lt;/em&gt; returns a special object which may or may not be defined. Thus, we need to check whether it is defined with &lt;em&gt;IsDefined&lt;/em&gt; before we can &lt;em&gt;Get&lt;/em&gt; its value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;List&amp;lt;char&amp;gt; EscapeChars = new List&amp;lt;char&amp;gt;
    { '\&amp;quot;', '\\', 'b', 'f', 'n', 'r', 't' };

Parser&amp;lt;char&amp;gt; ControlChar =
    from first in Parse.Char('\\')
    from next in Parse.EnumerateInput(EscapeChars, c =&amp;gt; Parse.Char(c))
    select ((next == 't') ? '\t' :
            (next == 'r') ? '\r' :
            (next == 'n') ? '\n' :
            (next == 'f') ? '\f' :
            (next == 'b') ? '\b' :
            next );

Parser&amp;lt;char&amp;gt; JChar =
    Parse.AnyChar
    .Except(Parse.Char('&amp;quot;')
    .Or(Parse.Char('\\')))
    .Or(ControlChar);

Parser&amp;lt;JSONLiteral&amp;gt; JString =
    from first in Parse.Char('&amp;quot;')
    from chars in JChar.Many().Text()
    from last in Parse.Char('&amp;quot;')
    select new JSONLiteral(chars, LiteralType.String);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To parse a string, we want to make sure that we allow for control characters (the control characters are all given on &lt;a href="http://json.org"&gt;json.org&lt;/a&gt;). As you can see, the string will be zero or more characters, which are in turn any character except a quotation mark or the escape character. Where &lt;em&gt;ControlChar&lt;/em&gt; is defined, &lt;em&gt;EnumerateInput&lt;/em&gt; is used on our list &lt;em&gt;EscapeChars&lt;/em&gt;. This instance of &lt;em&gt;EnumerateInput&lt;/em&gt; will return the following parser:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parse.Char( '\&amp;quot;').Or(Parse.Char('\\')).Or(Parse.Char('b')) ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is, it chains each element in &lt;em&gt;EscapeChars&lt;/em&gt; along as the parser &lt;em&gt;Parse.Char()&lt;/em&gt; using the &lt;em&gt;Or&lt;/em&gt; parser.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;JSONLiteral&amp;gt; JLiteral =
    JString
    .XOr(JNumber)
    .XOr(JBoolean)
    .XOr(JNull);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we're able to piece them all together to form our &lt;em&gt;JLiteral&lt;/em&gt; parser. Luckily, this is half of our entire parser!&lt;/p&gt;
&lt;p&gt;As you can see from our JSON BNF, the rest of the grammar is recursive. That is, self-referential. This is where &lt;em&gt;Ref&lt;/em&gt; will come in handy. We need to implement objects and arrays, and those two &lt;strong&gt;plus&lt;/strong&gt; literals will be defined as a value. So, let's define our &lt;em&gt;JValue&lt;/em&gt; parser, and proceed from there.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;JSONValue&amp;gt; JValue =
    Parse.Ref(() =&amp;gt; JObject)
    .Or(Parse.Ref(() =&amp;gt; JArray))
    .Or(JLiteral);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we are using &lt;em&gt;Ref&lt;/em&gt; to reference our yet-undefined &lt;em&gt;JObject&lt;/em&gt; and &lt;em&gt;JArray&lt;/em&gt; parsers. Of course, we've already created our &lt;em&gt;JLiteral&lt;/em&gt; parser, so we do not need to use &lt;em&gt;Ref&lt;/em&gt; to access it.&lt;/p&gt;
&lt;p&gt;Now we just need to parse JSON arrays and objects. For convenience, let's recall the portion of the JSON BNF which defined them:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bnf"&gt;&amp;lt;object&amp;gt;    ::= &amp;quot;{}&amp;quot; | &amp;quot;{&amp;quot; &amp;lt;members&amp;gt; &amp;quot;}&amp;quot;
&amp;lt;members&amp;gt;   ::= &amp;lt;pair&amp;gt; | &amp;lt;pair&amp;gt; &amp;quot;,&amp;quot; &amp;lt;members&amp;gt;
&amp;lt;pair&amp;gt;      ::= &amp;lt;string&amp;gt; &amp;quot;:&amp;quot; &amp;lt;value&amp;gt;
&amp;lt;array&amp;gt;     ::= &amp;quot;[]&amp;quot; | &amp;quot;[&amp;quot; &amp;lt;elements&amp;gt; &amp;quot;]&amp;quot;
&amp;lt;elements&amp;gt;  ::= &amp;lt;value&amp;gt; | &amp;lt;value&amp;gt; &amp;quot;,&amp;quot; &amp;lt;elements&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that &lt;em&gt;arrray&lt;/em&gt; and &lt;em&gt;object&lt;/em&gt; look very much alike, and the definition of &lt;em&gt;array&lt;/em&gt; appears to be a tad more simple. Therefore, we should write our &lt;em&gt;array&lt;/em&gt; parser first, and we can copy it down to create our slightly more complicated &lt;em&gt;object&lt;/em&gt; parser.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;IEnumerable&amp;lt;JSONValue&amp;gt;&amp;gt; JElements =
    JValue.DelimitedBy(Parse.Char(',').Token());

Parser&amp;lt;JSONValue&amp;gt; JArray =
    from first in Parse.Char('[').Token()
    from elements in JElements.Optional()
    from last in Parse.Char(']').Token()
    select new JSONArray(elements.IsDefined ? elements.Get() : null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how our &lt;em&gt;JElements&lt;/em&gt; parser almost perfectly matches the definition of &lt;em&gt;elements&lt;/em&gt; in the BNF. &lt;em&gt;DelimitedBy&lt;/em&gt; will parse any number of &lt;em&gt;JValue&lt;/em&gt; here, so long as they are separated by commas - this removes our need to call &lt;em&gt;JElements&lt;/em&gt; recursively. Our &lt;em&gt;JArray&lt;/em&gt; parser, then, just encases the &lt;em&gt;JElements&lt;/em&gt; parser in square brackets. If we desired we could combine the parsers into one. The reason I separated them here, however, was to demonstrate the close relationship between BNF and parsers like Sprache. Here is how the combined parsers would look:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;JSONValue&amp;gt; JArray =
    from first in Parse.Char('[').Token()
    from elements in
        JValue.DelimitedBy(Parse.Char(',').Token()).Optional()
    from last in Parse.Char(']').Token()
    select new JSONArray(elements.IsDefined ? elements.Get() : null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can move on to write our &lt;em&gt;JObject&lt;/em&gt; parser.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c#"&gt;Parser&amp;lt;KeyValuePair&amp;lt;string, JSONValue&amp;gt;&amp;gt; JPair =
    from name in JString
    from colon in Parse.Char(':').Token()
    from val in JValue
    select new KeyValuePair&amp;lt;string, JSONValue&amp;gt;(name.Value, val);

Parser&amp;lt;IEnumerable&amp;lt;KeyValuePair&amp;lt;string, JSONValue&amp;gt;&amp;gt;&amp;gt; JMembers =
    JPair.DelimitedBy(Parse.Char(',').Token());

Parser&amp;lt;JSONValue&amp;gt; JObject =
    from first in Parse.Char('{').Token()
    from members in JMembers.Optional()
    from last in Parse.Char('}').Token()
    select new JSONObject(members.IsDefined ? members.Get() : null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By now, this should all be trivial to you - especially considering &lt;em&gt;JObject&lt;/em&gt; and &lt;em&gt;JMembers&lt;/em&gt; are copies of &lt;em&gt;JArray&lt;/em&gt; and &lt;em&gt;JElements&lt;/em&gt;, respectively. With that, we should now be able to parse any document which conforms to the JSON standard. Notice that every JSON document is itself a single JSON object. Thus, given a JSON document, we would parse it with our &lt;em&gt;JObject&lt;/em&gt; parser.&lt;/p&gt;
&lt;p&gt;If you would like to see the parser in full, there is a version &lt;a href="https://github.com/IanWold/SpracheJSON/blob/master/SpracheJSON/JSONParser.cs"&gt;on my GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;My Work With Sprache&lt;/h1&gt;
&lt;p&gt;As I mentioned above, I've been working with Sprache for three years now, after seeing a presentation about it at the &lt;a href="http://twincitiescodecamp.com/"&gt;Twin Cities Code Camp&lt;/a&gt;, which is totally awesome and you should all go (it's even free).&lt;/p&gt;
&lt;p&gt;I've contributed to Sprache by adding a &lt;a href="https://ianwold.silvrback.com/parsing-comments-with-sprache"&gt;comment parser&lt;/a&gt;, I've published a &lt;a href="https://github.com/IanWold/SpracheJSON"&gt;JSON serializer/mapper&lt;/a&gt;, and I'm working on a &lt;a href="https://github.com/IanWold/SpracheDown"&gt;Markdown parser&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've used Sprache in small amounts in a couple other projects, and I enjoy using it wherever I'm able. An idea suggested to me at an &lt;a href="http://iowacodecamp.com/"&gt;Iowa Code Camp&lt;/a&gt;, which is also awesome and free and you should all go, was to write a tool to convert BNF into Sprache. I haven't done anything with this concept yet, but that is further work that could be done - if you're feeling the Sprache bug and you want to tackle that, go right ahead!&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;I'll keep this list updated as I encounter more on the interwebs. This list should provide a good base to continue exploring the topics introduced in this tutorial.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MSDN has &lt;a href="https://msdn.microsoft.com/en-us/library/bb397926.aspx"&gt;extensive documentation&lt;/a&gt; of LINQ.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Parser_combinator"&gt;Wikipedia&lt;/a&gt; provides an excellent starting point for learning more about parser-combinators&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/sprache/Sprache"&gt;Sprache GitHub&lt;/a&gt; links several examples, projects, and other tutorials.&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://stackoverflow.com/unanswered/tagged/sprache"&gt;StackOverflow tag&lt;/a&gt; receives regular traffic.&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 22 Jan 2016 00:00:00 Z</pubDate>
      <a10:updated>2016-01-22T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">sprachedown</guid>
      <link>https://ian.wold.guru/Posts/sprachedown.html</link>
      <title>SpracheDown</title>
      <description>&lt;p&gt;About a year and a half ago I attended one of the &lt;a href="http://www.twincitiescodecamp.com"&gt;Twin Cities Code Camps&lt;/a&gt;, and there I was shown a nifty library called Sprache. Sprache is, by no means, a novel invention. It's a monadic parser combinator based on years of programming done with similar libraries popular in functional languages. I say it's nifty because it seems to be the cleanest monadic parser combinator made in C#. It's also got a large enough following that keeps it up-to-date well enough, and it's what I was taught at the code camp.&lt;/p&gt;
&lt;p&gt;Half a year after that, almost a full year ago now, I attended my first &lt;a href="http://www.iowacodecamp.com"&gt;Iowa Code Camp&lt;/a&gt;. One guy I taught about Sprache and monadic parser combinators asked if it was possible to create a markdown parser with it, and my answer was a brief &amp;quot;Well, yeah, you can totally do that.&amp;quot; I've been mulling that over for about a year now, and I decided to look for a Markdown parser implemented with a monadic parser combinator to see what it would look like.&lt;/p&gt;
&lt;p&gt;I found an excellent &lt;a href="http://www.greghendershott.com/2013/11/markdown-parser-redesign.html"&gt;article&lt;/a&gt; by Greg Hendershott who implemented a MarkDown parser with a variation of Parsec for Racket. I browsed through his &lt;a href="https://github.com/greghendershott/markdown"&gt;parser&lt;/a&gt; on GitHub, and to my alarm it seemed that the parser itself was 1,000 lines long (I could be misreading that, I've no prior experience with Racket, but I'm assuming &amp;quot;parse.rkt&amp;quot; contains the parser). This compelled me to attempt such a parser with Sprache. If nothing else, it would be an interesting comparison.&lt;/p&gt;
&lt;p&gt;So I began writing this parser in steps, gradually adding features to it. I started out with headers, then lists, then paragraphs, and so forth. As I was writing this parser, I deliberately omitted features from MarkDown, notably reference-style links and inline HTML. I think if you can parse the majority of MarkDown the little bits could be implemented with equal ease, so I don't believe this invalidates the parser. I may go on and add inline HTML (Sprache ships with an XML parser example), but so long as this is a neat little pet project, I don't think I'll go too far beyond that.&lt;/p&gt;
&lt;p&gt;At first, I parsed the MarkDown text directly into strings representing the HTML output. This was efficient, but of course it wouldn't do for any parser - the user needs to be able to manipulate the output. To save time, I borrowed the syntax tree that Sprache's XML example comes with. I adapted and modified the objects with a couple methods to bend them to my will, so to speak, and from there it only took a minute to plug them into my parser.&lt;/p&gt;
&lt;p&gt;One problem that Mr. Hendershott faced was parsing MarkDown's nested list feature (this is achieved by inserting spaces before the asterisk in MarkDown). I don't know if this was due to the language he was using or a limitation of the parser,&lt;/p&gt;
&lt;p&gt;Now, I can't say I actually ran into any major problems when I implemented the parser. Granted, I ignored the MarkDown I didn't like so well, but as I said, I don't believe that invalidates the parser. In fact, my success with this parser speaks to the beauty inherent in parser combinators, specifically Sprache in this case. Over the past two days, I've spent a total of six hours working on this, and it's already relatively well-polished. The code is clear and readable, thoroughly commented, and the syntax tree is easily scalable. In addition, my parser is significantly smaller than Mr. Hendershott's parser (perhaps an advantage of Sprache in C# over Parsack in Racket?), and I don't believe I could top 1000 lines in the parser alone if I were to bring the parser up to speed with all of MarkDown's features.&lt;/p&gt;
&lt;p&gt;As I've been saying, I wrote this as a proof of concept, and I don't really intend for it to go anywhere, but if you think this is the coolest thing ever and you want it to be something, you're more than welcome to submit pull requests, or you can fork it and do your own thing with it. I believe I've documented everything thoroughly, so it should be easy to find your way around, but if I've missed something don't wait to contact me. Have fun!&lt;/p&gt;
&lt;p&gt;You can find SpracheDown on GitHub right &lt;a href="https://github.com/IanWold/SpracheDown"&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Fri, 26 Sep 2014 00:00:00 Z</pubDate>
      <a10:updated>2014-09-26T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">sprachejson</guid>
      <link>https://ian.wold.guru/Posts/sprachejson.html</link>
      <title>SpracheJSON</title>
      <description>&lt;p&gt;I've been meaning to write about this for a while now, but college got in the way of that. I wrote a deal I called SpracheJSON to parse JSON text into C# objects with Sprache, and it's kinda neat. I also played with the GitHub pages feature on this project, but that's not really interesting.&lt;/p&gt;
&lt;p&gt;I got this thing to the point where is parses the JSON just fine, no problem. But I also figured it made sense to serialize between JSON and C# objects. Like the other parsers I've written have ended up, it's got its own AST to throw the JSON into, but I can't imagine I'd ever want to use generic objects to store my data (isn't that the whole point of JSON?) That said, I've never been a fan of ISerializable, so I've got my own custom serializer thing going on. Obviously that makes it kinda imperfect, but that's how the world goes, I suppose.&lt;/p&gt;
&lt;p&gt;In the future I'm going to use ISerializable to make that part of the project go easier, but until then, I've got a nice half-baked parser here. I'm not going to detail the functionality of the library too much, but it's pretty slick, so you should definitely go check it out &lt;a href="https://github.com/IanWold/SpracheJSON"&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Fri, 10 Apr 2015 00:00:00 Z</pubDate>
      <a10:updated>2015-04-10T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">sprache_comments</guid>
      <link>https://ian.wold.guru/Posts/sprache_comments.html</link>
      <title>Parsing Comments with Sprache</title>
      <description>&lt;p&gt;I recently made a comment parser for the Sprache framework, and I wanted to give a basic run-down on how it works.&lt;/p&gt;
&lt;p&gt;The CommentParser class gives you the option to define the header styles for comments, and it can parse both single- and multi-line comments. It's rather basic as of right now, but that's (hopefully) subject to change in the future.&lt;/p&gt;
&lt;p&gt;Using CommentParser is pretty simple, but it's a tad different from the rest of the flow of Sprache as a combinator library. You'll need to make an instance of the CommentParser class, using the comment headers and (optional) newline character you require as arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static CommentParser comments = new CommentParser(&amp;quot;//&amp;quot;, &amp;quot;/*&amp;quot;, &amp;quot;*/&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From there, CommentParser gives you a couple parsers you can use to parse single- and multi-line comments:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static Parser&amp;lt;string&amp;gt; myParser = Parse.String(&amp;quot;foobar&amp;quot;).Text().Or(comments.AnyComment);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CommentParser.AnyComment will parse either single- or multi-line comments for you, while CommentParser.SingleLineComment and CommentParser.MultiLineComment will parse those individually.&lt;/p&gt;
&lt;p&gt;A real, working example using the CommentParser class can be found in Sprache's &lt;a href="https://github.com/sprache/Sprache/tree/master/src/XmlExample"&gt;XMLParser&lt;/a&gt; example.&lt;/p&gt;
&lt;p&gt;In the future, it would be awesome if multiple comment headers could be included, and if whitespace could be defined to include comments. Some work towards this effort has been done on my GitHub &lt;a href="https://github.com/IanWold/Sprache/blob/Comments/src/Sprache/CommentParser.cs"&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Fri, 06 Feb 2015 00:00:00 Z</pubDate>
      <a10:updated>2015-02-06T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">successful_personal_projects</guid>
      <link>https://ian.wold.guru/Posts/successful_personal_projects.html</link>
      <title>Successful Personal Projects</title>
      <description>&lt;p&gt;I have this one specific psychic power, when I speak to a software engineer I can tell them how many half-finished, abandoned, sometimes barely-started personal projects they have scattered between source control providers and dusty hard drives. I can even prove to you I have this psychic power! Just focus your mind on your favorite color, and I'll tell you now how many such projects you have. Ready? The answer that's coming to me is &amp;quot;a lot.&amp;quot;&lt;/p&gt;
&lt;p&gt;Uncanny, isn't it?&lt;/p&gt;
&lt;p&gt;We all spend a lot of time with software, and we spend a lot of time thinking about cool software things we can do. We know we have the ability to create these things, we see others releasing their fun projects, but this or that happens and our project stalls. Then the next one and the next one, to the point that for every successful project we've released we have several unfinished ones.&lt;/p&gt;
&lt;p&gt;This isn't a problem in itself; they're on our own time and impacting nobody. However, we did start each project for &lt;em&gt;some reason&lt;/em&gt; (presumably), and failing to have finished a project is to have failed to get what we wanted from it. Maybe we wanted to learn a new technology, fully develop an idea, or have the satisfaction of having built something. Consistently engaging with personal projects - and that means finishing them - comes with a number of benefits in increasing our abilities and confidence in our work.&lt;/p&gt;
&lt;p&gt;I'm not going to claim to be great at finishing personal projects, but I can claim to frequently see folks stepping in the same potholes I've learned to avoid, so I can share my approach to personal projects.&lt;/p&gt;
&lt;h2&gt;1. What do you want to gain?&lt;/h2&gt;
&lt;p&gt;The most important consideration for a personal project is having a realistic personal goal. Not a product goal necessarily, but a &lt;em&gt;personal&lt;/em&gt; project is done, ultimately, to fulfil a &lt;em&gt;personal&lt;/em&gt; need or want. What is that goal? Are you trying to learn a framework, a pattern, a protocol, or a language? Are you trying to gain a deeper understanding of a technology you commonly use? Are you trying to deliver some kind of product - for yourself, a friend, or a club? Are you trying to showcase your work to colleagues, potential employers, or a blog audience?&lt;/p&gt;
&lt;p&gt;I'm under no illusions that the majority of personal projects are begun under the motivation of creating some end result; most are not contrived after the decision upon the &lt;em&gt;personal&lt;/em&gt; goal. That's entirely fine, and I think it's short-sighted to consider such a workflow as putting the cart before the horse. I'm definitely more inspired thinking about delivering a cool thing than I am by a satisfaction with being able to bring up cool facts about &lt;a href="https://www.rfc-editor.org/rfc/rfc1605"&gt;RFC 1605&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nonetheless, before you start building you must be upfront about what the personal goal is and make sure you define it well. To reiterate, because these projects are done on &lt;em&gt;personal&lt;/em&gt; time with &lt;em&gt;personal&lt;/em&gt; resources, they won't be successful without being rooted firmly in &lt;em&gt;personal&lt;/em&gt; aims.&lt;/p&gt;
&lt;h2&gt;2. Define done&lt;/h2&gt;
&lt;p&gt;Indeed, there's no escaping the same &amp;quot;definition of done&amp;quot; we must engage in at work. The only real difference being that your definition of done for your personal project should be significantly lower in scope than you have at work. Descope everything and MVP-max. It's not glamorous but it is realistic. Sure I know that I &lt;em&gt;can&lt;/em&gt; deliver a set of 12 features for a project, but I also know that I &lt;em&gt;definitely will&lt;/em&gt; deliver the project if it's only 3 of those 12 features. It's not insulting to my abilities to descope the project, it's a guarantee that I can achieve what I want to achieve. Remeber, delivering the resulting software is subordinate to filling my personal requirements.&lt;/p&gt;
&lt;p&gt;Done means two things: the software is working at the state that I want &lt;em&gt;and&lt;/em&gt; I have achieved my personal goal. These go hand-in-hand: the software and technology need to support the personal goal, and the personal goal constrains the scope of the software. Just as I'm focusing on a single personal goal, I'm focusing on achieving a single piece of software, that does one thing. &lt;a href="https://goodinternetmagazine.com/my-website-is-ugly-because-i-made-it/"&gt;It's okay if it's a little ugly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The faster I can get to &amp;quot;done,&amp;quot; the better. Sensible personal goals and super-MVP-ified software gets me there. This always leaves extra bandwidth at the end of the project for me to continue it. It's a separate project in itself, often, to add more features to this piece of software - here we can rinse and repeat the personal project cycle. Or, I can publish the project open source and have a very helpful backlog of interesting addons. Sometimes others on the internet do engage with your projects this way!&lt;/p&gt;
&lt;h2&gt;3. Architect and develop pragmatically&lt;/h2&gt;
&lt;p&gt;Or, put another way, focus on the core competency of the project. If your project requires auth but your goal is to learn about VOIP, don't write your own auth layer. If your goal is not to learn a new language then use a familiar one. There's no need to dress your codebase up with all of the bells and whistles endemic to professional engineering. C# and Java code tends to be riddled with too many interfaces, passthrough layers, and unit tests. Your personal projects sute don't need unit tests, not out of the gate at least.&lt;/p&gt;
&lt;p&gt;Architecture is a very common pitfall. Really consider what you actually need. If you can get away with hosting your site on GitHub pages then do that. If you only absolutely require one small piece of server functionality then spin up a serverless function (I love &lt;a href="https://docs.railway.com/reference/functions"&gt;Railway's functions&lt;/a&gt; for this). Seriously interrogate every piece of infrastructure. Do you &lt;em&gt;need&lt;/em&gt; Postgres or can you get away with a JSON file or &lt;a href="https://www.levels.fyi/blog/scaling-to-millions-with-google-sheets.html"&gt;Google Sheets&lt;/a&gt;? Do you &lt;em&gt;need&lt;/em&gt; a server or can you serve using existing technologies? Do you &lt;em&gt;need&lt;/em&gt; all your favorite NPM packages or do you just need &lt;a href="https://ian.wold.guru/Posts/i_like_petite_vue.html"&gt;simple reactivity&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Remember you've only got one goal, not one main goal and a bunch of secondary ones. I'm sure you'd really enjoy learning a new JS framework, but if that isn't the one goal of your project then that's not what we're going to be doing on &lt;em&gt;this&lt;/em&gt; project, we'll be doing it in another project. Conversely, if your one goal &lt;em&gt;is&lt;/em&gt; to learn a JS framework then our software isn't going to be using an unfamiliar or burdensome architecture, and it definitely should have similar functionality to other projects you've developed before!&lt;/p&gt;
&lt;p&gt;To keep development from stalling, I specifically keep two things in mind. First, I want to get to an executable piece of software as step #0 in development; I won't start writing any code until I can execute code, and then I'll keep the project ni a state of being able to run. Second, I'll always focus on the difficult and &amp;quot;core competency&amp;quot; component first, at the exclusion of anything else; if a CSS style is taking too long I'll let it be ugly until I can get the core component where I want it.&lt;/p&gt;
&lt;h2&gt;4. Finish it&lt;/h2&gt;
&lt;p&gt;Every step you take on the project should be a tangible, obvious step towards the end. Because you've kept your project scoped as low as possible you'll have the time and energy to do the coding work, and because you're clear about your personal goal you'll identify when you've achieved that.&lt;/p&gt;
&lt;p&gt;If you're spinning your wheels you'll need to reevaluate what you haven't scoped down. Sometimes we make mistakes, sometimes projects take off on the wrong trajectory and need to be seriously rethought. Sometimes projects do need to be killed - not abandoned, but killed. I advise restarting with the same personal goal and software concept, but after a rearchitect. That itself might be an architecture project!&lt;/p&gt;
&lt;p&gt;When we finish something we get the satisfaction of having finished, the confidence that we &lt;em&gt;can&lt;/em&gt; finish it, and we've gained whatever knowledge, experience, or product that we set for ourselves.&lt;/p&gt;
</description>
      <pubDate>Wed, 02 Jul 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-07-02T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">testing_logging_in_asp_net_core</guid>
      <link>https://ian.wold.guru/Posts/testing_logging_in_asp_net_core.html</link>
      <title>Testing Logging in ASP.NET Core</title>
      <description>&lt;p&gt;While I'm not much a fan of unit tests, I'm a big fan of integration tests, particularly for distributed services. I'm in agreement with Grug that &lt;a href="https://grugbrain.dev/#grug-on-testing"&gt;this is the right level for long-lived automated tests&lt;/a&gt;, and I try to structure my projects in such a way that everything which I need to test can be and is tested this way. Now, this strategy doesn't apply to every project type, but any project that can be seen as a request-response black box - like an ASP.NET Core API - is a proper candidate.&lt;/p&gt;
&lt;p&gt;A lot of API integration testing will focus on HTTP requests and the responses to those and call it a day. That's perfectly fine, and surely captures most all the necesssary business logic to test. We would be mistaken though to think these are the only inputs and outputs to an API. If you're running in production, I'm sure your API is able to be configured by a DevOps administrator, and maybe it publishes events to an event queue or schedules emails to be sent. These are all different inputs and outputs that can also be tested. One output of our services that often goes untested are their logs.&lt;/p&gt;
&lt;p&gt;Why would we need to test log output? You might have many reasons, but I suspect the primary reason would be if you have a monitoring system reading the logs from the API with alert conditions set up in specific cases. Suppose you have a critical path in your API and you want to get pinged about any errors in this flow. You might configure your monitoring service to send you a text if it ever sees an ERROR-level log with text &amp;quot;critical failure.&amp;quot; This log output has just become a business rule, and you may well want to keep a test to ensure that if the API ever enters that condition, the log with the correct level and string will be output, as expected by the monitoring suite.&lt;/p&gt;
&lt;p&gt;At least, that's the condition I found myself in recently, needing to ensure certain logging conditions are met for specific failure situations. So, I'll document how I went about setting these up for an integration test suite for an ASP.NET Core API. My whole sample solution and all the code on this article &lt;a href="https://github.com/IanWold/AspNetCoreTestLogOutput/tree/main"&gt;can be found on my GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To set up the tests, we just need a simple ASP application with an endpoint that logs an error:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var app = WebApplication.CreateBuilder(args).Build();

app.MapPut(&amp;quot;/test&amp;quot;, (ILogger&amp;lt;Program&amp;gt; logger) =&amp;gt;
    logger.LogError(&amp;quot;Test error!&amp;quot;)
);

app.Run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you start a new project today, you'll find that the ASP templates will set you up with a &amp;quot;classless&amp;quot; &lt;code&gt;Program.cs&lt;/code&gt; file, but this is a bit of an issue when it comes to testing as we need to make the &lt;code&gt;Program&lt;/code&gt; class public &lt;em&gt;or&lt;/em&gt; internally visible to the test project. To set up the demonstration I just updated &lt;a href="https://github.com/IanWold/AspNetCoreTestLogOutput/blob/main/AspNetCoreTestLogOutput.Api/Program.cs"&gt;my Program.cs file&lt;/a&gt; to use an actual Program.Main method.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Program&lt;/code&gt; class is used in our test code to set up a client to which we can send HTTP requests. I'll use XUnit to set this test up, but this solution works with any test runner. (Note that the test code requires you to add the Nuget packages &lt;a href="https://www.nuget.org/packages/Microsoft.Extensions.Logging"&gt;Microsoft.Extensions.Logging&lt;/a&gt; and &lt;a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Testing"&gt;Microsoft.AspNetCore.Mvc.Testing&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[Fact]
public async Task TestErrorLogged()
{
    var client = new WebApplicationFactory&amp;lt;Program&amp;gt;().WithWebHostBuilder(builder =&amp;gt; {}).CreateClient();
    var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Put, &amp;quot;/test&amp;quot;));
    Assert.True(response.IsSuccessStatusCode);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we run this test now we'll see it passing. The &lt;code&gt;/test&lt;/code&gt; endpoint is logging an error, but note that it is always responding 200 by default. Now, we need to capture that error log and update our test to check for it.&lt;/p&gt;
&lt;p&gt;The web host builder, which we configure with the helpfully-named &lt;code&gt;WithWebHostBuilder&lt;/code&gt;, allows us to alter the configuration and services of the ASP app. You can think of it as ammending the normal setup logic in &lt;code&gt;Program&lt;/code&gt; so that the tests can inject their own configuration, replace services with fakes, or any other alterations that might be needed to set up the test.&lt;/p&gt;
&lt;p&gt;In this case, we're going to want to configure how the ASP app deals with logs. ASP is set up with a default &lt;em&gt;logger provider&lt;/em&gt; which it uses to resolve the &lt;code&gt;ILogger&amp;lt;Program&amp;gt;&lt;/code&gt; dependency on our endpoint. We're going to want to write our own &lt;code&gt;ILogger&lt;/code&gt; to be injected there, and in order for ASP to use our &lt;code&gt;ILogger&lt;/code&gt; we'll also need to write our own &lt;code&gt;ILoggerProvider&lt;/code&gt; and configure our app-under-test to use this logger provider.&lt;/p&gt;
&lt;p&gt;These interfaces are quite easy to implement, especially since we only need to store logs in-memory so we can search through them in the test. I'll use a &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1?view=net-9.0"&gt;ConcurrentBag&lt;/a&gt; as the collection type to avoid any possible concurrency issues.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ILogger&lt;/code&gt; only contains three methods, and we'll really only be interested in the &lt;code&gt;Log&lt;/code&gt; message, in which we'll simply add the log into the in-memory &lt;code&gt;ConcurrentBag&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public record LogMessage(LogLevel LogLevel, string Message);

public class TestLogger(ConcurrentBag&amp;lt;LogMessage&amp;gt; logs) : ILogger
{
    public void Log&amp;lt;TState&amp;gt;(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func&amp;lt;TState, Exception?, string&amp;gt; formatter) =&amp;gt;
        logs.Add(new(logLevel, formatter(state, exception)));

    public bool IsEnabled(LogLevel logLevel) =&amp;gt; true;

    public IDisposable? BeginScope&amp;lt;TState&amp;gt;(TState state) where TState : notnull =&amp;gt; null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ILoggerProvider&lt;/code&gt; only has &lt;em&gt;one&lt;/em&gt; method which returns an &lt;code&gt;ILogger&lt;/code&gt; implementation given some category name. We don't care about the category name, so we can just return our custom &lt;code&gt;ILogger&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class TestLoggerProvider : ILoggerProvider
{
    private readonly ConcurrentBag&amp;lt;LogMessage&amp;gt; _logs = [];

    public IReadOnlyCollection&amp;lt;LogMessage&amp;gt; Logs =&amp;gt; _logs;

    public ILogger CreateLogger(string categoryName) =&amp;gt; new TestLogger(_logs);

    public void Dispose() =&amp;gt; GC.SuppressFinalize(this);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I personally prefer to not expose a &lt;code&gt;ConcurrentBag&lt;/code&gt; and instead expose it as an &lt;code&gt;IReadOnlyCollection&lt;/code&gt;, but that's a stylistic preference.&lt;/p&gt;
&lt;p&gt;The only thing left is to revisit our test method - we need to configure our ASP app to use this logger provider, and we should add a test for the error log. We can use the &lt;code&gt;ConfigureLogging&lt;/code&gt; method on the web host builder to ensure our custom logger provider is the only provider, and to make sure logs at all levels are captured. After we send the request, we can then inspect the logs in our custom logger provider to confirm the expected log was logged.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[Fact]
public async Task TestErrorLogged()
{
    var loggerProvider = new TestLoggerProvider(); // [tl! focus]
    var client = new WebApplicationFactory&amp;lt;Program&amp;gt;()
        .WithWebHostBuilder(builder =&amp;gt; // [tl! focus:7]
            builder.ConfigureLogging(logging =&amp;gt;
            {
                logging.ClearProviders();
                logging.AddProvider(loggerProvider);
                logging.SetMinimumLevel(LogLevel.Trace);
            })
        )
        .CreateClient();

    var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Put, &amp;quot;/test&amp;quot;));

    Assert.True(response.IsSuccessStatusCode);
    Assert.Contains(loggerProvider.Logs, l =&amp;gt; l.LogLevel == LogLevel.Error &amp;amp;&amp;amp; l.Message.Contains(&amp;quot;Test error&amp;quot;)); // [tl! focus]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's all! Again, all the code above is in &lt;a href="https://github.com/IanWold/AspNetCoreTestLogOutput/tree/main"&gt;a working solution on my GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 12 Jan 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-01-12T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">theres_always_money_in_the_banana_stand</guid>
      <link>https://ian.wold.guru/Posts/theres_always_money_in_the_banana_stand.html</link>
      <title>There's Always Money in the Banana Stand</title>
      <description>&lt;p&gt;The penultimate fallacy of distributed computing is &amp;quot;transport cost is zero.&amp;quot; Anyone who has stood up their own test project on a cloud provider can intuit the fallacy here: dispatching any message across a transport layer requires more processing and uses network resources, each of which cost runtime and ultimately money for our application. In the context of a distributed system these costs can end up being huge, making it important to not just avoid the fallacy but to always be cognizant of the costs associated with any transport.&lt;/p&gt;
&lt;p&gt;Naturally there are plenty of options to mitigate this issue, as I've explored on previous posts in this series. If (de) serialization is causing poor performance at the application level, &lt;a href="https://msgpack.org/"&gt;MessagePack&lt;/a&gt; or &lt;a href="https://protobuf.dev/"&gt;ProtoBuf&lt;/a&gt; can save you a lot of resources - &lt;a href="https://www.infoq.com/news/2023/07/linkedin-protocol-buffers-restli/"&gt;LinkedIn reduced latency by 60% with ProtoBuf&lt;/a&gt;. Keeping inter-service communication on a local network and geolocating friendly services will further reduce latency and isolate bandwidth concerns.&lt;/p&gt;
&lt;p&gt;I think the 80/20 rule applies here - 80% of the achievable cost reduction will take 20% of the effort. That's not to say that it's easy work, but that it's &lt;em&gt;relatively&lt;/em&gt; easy. How easy do you think it would be to switch your production distributed system to ProtoBuf? Now imagine how much work there is in the other 80%! I take this to indicate that there's a practical plateau as far as cost saving is concerned - at a certain point the juice is no longer worth the squeeze. That said, even if you were to realize &lt;em&gt;all&lt;/em&gt; the potential cost savings in a distributed system, you &lt;em&gt;are still&lt;/em&gt; distributing, which is inherently more costly than not distributing.&lt;/p&gt;
&lt;p&gt;With everything optimized, you still incur a baseline cost in latency and network resource consumption with the message. On either end of that message, you incur a serialization cost. You incur a cost from the systems required to guarantee your required level of resiliency and fault tolerance (retry logic, load balancers, etc), and you incur a cost from your security systems (firewall, auth, etc).&lt;/p&gt;
&lt;p&gt;In a way, each of the previous fallacies has preempted this fallacy, so there's not a lot to say here that isn't review. The key lesson here is that if you are distributing, then you are spending more money, period. Depending on your domain you might be spending a lot more money than if you weren't distributing. There are plenty of problems with monolithic systems, and while some of those problems are only solvable with distribution, most of the common problems that arise in these systems are just regular problems. Distribution isn't the magic wand to unspaghetti your bad codebase.&lt;/p&gt;
&lt;p&gt;Looking more broadly than the costs associated in just transporting one message, you will incur costs in the telemetry, logging, monitoring, and alerting required to properly maintain a distributed system, even if your observability requirements aren't terribly deep. You'll incur costs with the engineering resoruces required to develop these systems, the operations resources required to deploy and maintain these systems, and the organizational costs required to develop and enforce the procedures that arise from owning these systems.&lt;/p&gt;
&lt;p&gt;These fallacies demonstrate that all of these costs are &lt;em&gt;necessary&lt;/em&gt; for distributed systems, and they reinforce the big leap in effort to create and maintain these systems. Taken all together, I see the fallacies all pointing to one glaringly obvious conclusion: unless the problem you're trying to solve cannot be solved but by distributing, then don't distribute. Maybe you've got a problem-ridden legacy monolith and you're looking at a large expenditure involved in refactoring. Microservices look attractive in this situation, but beware of underestimating the proper costs associated with the microservices transformation. Remember &lt;a href="https://grugbrain.dev/#grug-on-microservices"&gt;Grug on Microservices&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;grug wonder why big brain take hardest problem, factoring system correctly, and introduce network call too&lt;/p&gt;
&lt;p&gt;seem very confusing to grug&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Fri, 30 Aug 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-08-30T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">there_is_one_admin</guid>
      <link>https://ian.wold.guru/Posts/there_is_one_admin.html</link>
      <title>There Are Infinite Administrators</title>
      <description>&lt;p&gt;In my recent exploration of the eight Fallacies of Distributed Computing, the first five have all regarded, almost exclusively, the technical aspects of distributed systems. However, as is the case for every software project, the human aspect is equally important. Each project is a combination of technical knowledge and humans - intelligent and flawed - capable of wielding it. This fallacy however - that there is one administrator - is almost entirely about the people aspect, and I'm very excited about that.&lt;/p&gt;
&lt;p&gt;One of the things I find fun about producing small, personal projects (like &lt;a href="https://freeplanningpoker.io/"&gt;FreePlanningPoker.io&lt;/a&gt;) is that I can know everything about the system. I know exactly how to build, deploy, and monitor the system, through the code, configuration, and cloud environment. I am capable of being the &lt;em&gt;one administrator&lt;/em&gt; for this system.&lt;/p&gt;
&lt;p&gt;The systems we're generally paid to develop, however, are rarely as simple as our small, personal projects. Indeed, even our personal projects can grow to the point that we don't know everything about them! That's generally the point where my velocity on them drops off a cliff, but that's another matter. What I mean to get at though is that our professional projects tend to be the size or complexity (often both) that there can't possibly be one administrator - one person (not one DevOps team, not one manager, but one individual human) who knows everything about running the system.&lt;/p&gt;
&lt;p&gt;On the whole, it's not even advisable to only have one administrator even if it's possible for a given system. People come and go from their positions or firms with some frequency, or sometimes they just forget things; knowledge and experience needs to be distributed throughout the firm. As projects grow in size and complexity it's generally more efficient anyway to distribute the administration across the firm. And not only does our company have multiple administrators, the administrators aren't the only administrators! Engineers are administrators, managers are, BAs are. External companies can be, too, and that idea becomes very interesting considering cloud providers might introduce a &lt;em&gt;lot&lt;/em&gt; of potential administrators.&lt;/p&gt;
&lt;p&gt;The problem of course is that each administrator has a different understanding of the system, and sometimes these understandings conflict with each other. Each administrator's understanding of the system changes over time, and to successfully keep our system going they all need to interact with each other, with their overlapping knowledge. These administrators aren't just tasked with observing the system to diagnose problems, but they also need the ability to change the system in various ways. This introduces the issue that they can - and will - change our system architecture &lt;em&gt;on the fly&lt;/em&gt;! &lt;em&gt;Ahem&lt;/em&gt; &lt;a href="https://ian.wold.guru/Posts/topology_doesnt_change.html"&gt;topology doesn't change&lt;/a&gt; &lt;em&gt;ahem&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So our systems need to be open to change from various sources, and resilient enough to keep running if these changes aren't, shall we say, properly thought out. They can't become reliant on huge configuration files though, as we lose the ability to teach others about the workings of the system. Finally, they do need to have clear and high quality observability so that folks who might not be terribly knowledgeable about their workings can diagnose problems. This is particularly key in our distributed environment, where problems are frequently the result of many complex interactions between various components.&lt;/p&gt;
&lt;h1&gt;Service Design&lt;/h1&gt;
&lt;p&gt;Aside from some specific considerations regarding configurability, this fallacy doesn't inform our service design more than the other fallacies do, but it does serve to reinforce many of the same learnings we've taken from the others. It's another pin in the hat to reinforce our understanding that distributed systems are &lt;em&gt;difficult&lt;/em&gt; and do genuinely require that much extra care.&lt;/p&gt;
&lt;h2&gt;Decoupling and Isolation&lt;/h2&gt;
&lt;p&gt;One of the primary themes (for lack of a better word) around developing distributed systems, and consequently my writing on these fallacies, is isolation. This is a double-edged sword which too easily looks like a cudgel to wield against any of the destabilizing factors which affect distributed services. On one hand, after doing the difficult task of identifying the unstable areas of a system, it's very easy and obvious to say that this is the part we need to code defensively against.&lt;/p&gt;
&lt;p&gt;Indeed, there should be some such defense. The trouble comes in that each layer of defense is its own layer of complexity. Never forget &lt;a href="https://grugbrain.dev/"&gt;evil demon spirit complexity&lt;/a&gt;. Isolating and decoupling services are thus better suited as guiding principles when approaching architectural decisions, with the goal being to find the simplest architecture, most parsimonious with the domain requirements, maximizing te degree of isolation without growing the architecture. This is a major reason that message queues come up so frequently in discussions regarding distributed systems; I think it's fair to say they &lt;em&gt;can&lt;/em&gt; provide the most intelligent sort of isolation.&lt;/p&gt;
&lt;p&gt;This is all with respect to the sometimes &lt;em&gt;interesting&lt;/em&gt; effects which administrators can have on the system. In short, administrators can be the direct causes of the problems we explored with the other fallacies, so in a sense we are providing some degree of protection against the crazy and whacky admins by following best practices there.&lt;/p&gt;
&lt;p&gt;Admins can (and will) &lt;a href="https://ian.wold.guru/Posts/topology_doesnt_change.html"&gt;change around the network topology&lt;/a&gt;, &lt;a href="https://ian.wold.guru/Posts/the_network_is_secure.html"&gt;alter security management and configuration&lt;/a&gt;, and tinker with any number of properties (particularly with respect to cloud providers) that can cause any number of &lt;a href="https://ian.wold.guru/Posts/the_network_is_reliable.html"&gt;network unreliabilities&lt;/a&gt;. Those articles which I linked are focused on &lt;em&gt;guarding against&lt;/em&gt; these negative properties of distributed systems, however the outlook is slightly different when we're considering this from the administration perspective. Admins &lt;em&gt;need&lt;/em&gt; to have the ability to change these things, even though they &lt;em&gt;can't&lt;/em&gt; know all of the deleterious ripple effects a change might have throughout the system. This means that the systems we create need to go the extra step to &lt;em&gt;enable&lt;/em&gt; these changes to happen to the system.&lt;/p&gt;
&lt;h2&gt;Configuration and Observability&lt;/h2&gt;
&lt;p&gt;I had tried writing about these two under separate headers so as to explain them in more detail, but as far as the administrators are concerned they go hand-in-hand. The configurability of the system allows the administrators to be able to adapt the system in response to new errors, a change in the environment, or whatever they might need to do. Typically, it's the observability of the system that is able to give them the insight that something changed, there's a problem, and points to where the fix needs to go. Sure, often we do need to reengineer parts of our system's components in response to errors we observe. However, as we've discussed at length on this series on the fallacies, a lot of system errors come in through the environment and configurations, and configuration &lt;em&gt;can&lt;/em&gt; be the solution - if we let it.&lt;/p&gt;
&lt;p&gt;Both the observability of the whole system and the configuration of each individual component needs to be &lt;em&gt;obvious&lt;/em&gt;. Remember - there is no one individual with everything in their head, nor can we count on everyone's knowledge being up-to-date. As engineers, we tend to talk a lot about self-documenting code, but remember that my observability graphs and the system configurations should also strive to achieve the same level of clarity!&lt;/p&gt;
&lt;p&gt;Clarity being the most important factor in designing this aspect of our system, it should also be designed to fit its &lt;em&gt;purpose&lt;/em&gt; - observability should be strictly focused on identifying &lt;em&gt;what&lt;/em&gt; issues are happening, &lt;em&gt;why&lt;/em&gt; they are happening, and &lt;em&gt;where&lt;/em&gt; they are happening. A report on the throughput of the entire system is useless, as while it might identify that throughput drops too low it cannot identify where the issue is. Nonspecific reports shouldn't be included, and each report needs to be able to be correlated - easily - to activity and system components.&lt;/p&gt;
&lt;p&gt;Then too there's a potential problem in moving from an identified issue to a configuration fix in a particular area of the system. Configurations need to be designed with a particular mind for &lt;em&gt;who&lt;/em&gt; might be updating these configurations and &lt;em&gt;why&lt;/em&gt;. Just the other week I was attempting to diagnose an issue in a microservices architecture and identified that I wanted to attempt increasing the timeout policy for outbound messages from a microservice, only to open up its config file and find a separate timeout configuration for &lt;em&gt;each&lt;/em&gt; outbound request, each named for the method in its code which made the outbound request. Needless to say, I increased every one seeing as I wasn't going to spend a day reading its source.&lt;/p&gt;
&lt;h2&gt;DevOps and Deployment&lt;/h2&gt;
&lt;p&gt;Sometimes I take this for granted (and you might too) but it's worth writing out: Continuous integration and continuous delivery (CI/CD) is one of the most important practices for &lt;em&gt;any&lt;/em&gt; software system, distributed or not. These practices emphasize that engineers should &lt;em&gt;continuously&lt;/em&gt; integrate their working code with the main codebase, and that the software should be &lt;em&gt;continuously&lt;/em&gt; delivered as updates are made to it.&lt;/p&gt;
&lt;p&gt;To me, the gold standard for components of a distributed system is to deploy automatically when &lt;em&gt;any&lt;/em&gt; code is integrated into the main branch. This requires a fair amount to be in place in order to be effectively supported. First and most obviously, you do need a build gate guarding the main branch, and it's incredibly unwise to not include a comprehensive suite of automated tests along with that. You do need some kind of review process so that a single engineer can't yeet whatever they please into prod, and both the review and build processes need to be fast - like, measured in minutes fast. Finally, you &lt;em&gt;must&lt;/em&gt; have appropriate monitoring and a people process around reacting to it.&lt;/p&gt;
&lt;p&gt;With all of these in place, I &lt;em&gt;can&lt;/em&gt; get features out quickly, but importantly I can get a bugfix out quickly. At my current firm, I've been able to have prod issues discovered, triaged, coded, reviewed, and deployed in 20 minutes.&lt;/p&gt;
&lt;p&gt;These practices serve to reinforce robustness and redundancy in the system not by giving us a way to roll back, but a way to roll forward. I notice that having more components - particularly distributed components - in a system makes it more and more difficult for any one component to be able to roll back in the case of a failure. These system components are all connected by (sometimes not entirely explicitly defined) contracts that cause a high level of complexity in any system, well-maintained or not. Rolling forward is sometimes - a lot of the time even - the only realistic option.&lt;/p&gt;
&lt;h1&gt;Process Design&lt;/h1&gt;
&lt;p&gt;If the technical considerations I listed above seem a bit surface-level and handwaving at my previous writing, I think you're getting the same feeling that I do. As I mentioned before, this fallacy really concerns the human aspect of software development more than the technical aspect - that there is more than one &lt;em&gt;human&lt;/em&gt; administrator is a recognition that impacts how we design the &lt;em&gt;human&lt;/em&gt; administration of the system.&lt;/p&gt;
&lt;p&gt;To reiterate, these human administrators need to be able to jump into parts of a system at any time and understand what it is doing, if something is going wrong, and the options available to them if something is going wrong. Then they need to be able to manipulate the system (as much as they can reasonably be afforded) without needing to engage engineers to change the code. Indeed, these administrators might well be engineers too, but when facing a high-impact incident it's really nice to be able to click a few buttons to get customers back up and going.&lt;/p&gt;
&lt;p&gt;Each human administrator is going to have a different understanding of the system. Some will have a greater familiarity with larger parts of the system, but it's incredibly rare that anyone knows everything about the system. To complicate it, our memories aren't as solid as we typically like to think. Each person's knowledge of the system will change over time, and that doesn't just mean that they will eventually become more and more familiar with the system. On the contrary, they will forget certain aspects or old knowledge might become stale as components or the environment updates over time. Yet, each one needs to be able to administer the system, particularly if you're not sure which of them might be able to respond to the 2 AM P1 on Christmas.&lt;/p&gt;
&lt;h2&gt;Knowledge Sharing&lt;/h2&gt;
&lt;p&gt;This is maybe the most crucial thing to integrate into our processes at all levels - it's not just for administrators! This is the only tool you have to &lt;a href="https://lananovikova.tech/posts/bus-factor/"&gt;improve your bus factor&lt;/a&gt;, and the only tool you have to increase the creativity and maximize the contribution from your whole team. Continuously knowledge sharing is the boyscout juice of productivity.&lt;/p&gt;
&lt;p&gt;But today we're talking about administrators, so today we'll share knowledge about them. Adopting a continuous, diligent practice of sharing knowledge will help administrators understand &lt;em&gt;how to understand&lt;/em&gt; the system, the options they have for manipulating it, and staying abreast of changes and updates.&lt;/p&gt;
&lt;p&gt;I have no evidence for this claim, but it seems to me that the majority of teams and firms have a limited approach to knowledge sharing, and I wonder if this would be one of those cases where if we polled the teams on how they felt that they would feel that they do a better job of it than they actually do. I mean to say that there tends to be a bit of complacency here - if Bob really knows a particular API, it's just &lt;em&gt;easier&lt;/em&gt; to only go to him to address problems. Over time everyone might just start calling it &amp;quot;Bob's API&amp;quot;, and over even more time everyone absolves themselves of needing to know anything about it - Bob always takes care of it! Bob's API has an unenviable bus factor, and the kicker is that none of this is even Bob's fault, even though his name is on it now!&lt;/p&gt;
&lt;p&gt;Knowledge sharing takes a lot of forms, but it's inextricably linked with work sharing. Lectures and lunch-and-learns are great ways for an individual to offload knowledge, but they're not a great way for the others to ingest knowledge. The best way for individuals to offload knowledge are through active-engagement exercises like hackathons or pair work. The &lt;em&gt;very&lt;/em&gt; best way to transfer knowledge is the &amp;quot;thrown in the deep end of the pool&amp;quot; approach, but this can also have detrimental effects if the target doesn't first know how to swim.&lt;/p&gt;
&lt;p&gt;The best underutilized tool to get knowledge from one person to another is repetition. If our gradeschool teachers had only once told us that the mitochondrea is the powerhouse of the cell, it would not have stuck with us. They didn't tell us once though, they told us this once per year &lt;em&gt;at least&lt;/em&gt;, and that repetition has reinforced that knowledge in all of us, to the benefit of meme culture a decade ago. If you have particular knowledge about key points regarding specific aspects of the system, you should repeat these points regularly across the various forms of communication you have with your organization. You don't need to start each morning in Slack with the same sentence, or set that sentence as your email signature (that's an example I just came up with but I might actually try that now). Rather, for these bits of knowledge which you can deliver as a soundbite, take the opportunity to do so just whenever it's relevant just with larger groups of people. &lt;em&gt;Particularly&lt;/em&gt; do this for those more ephemeral, touchy-feeling aspects of the system - there's a lot more knowledge which we can convey in feelings than we typically consider.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;I will be the first to accuse myself of taking the typical software engineer's approach to writing documentation - that is, I tend not to, at least for technical documentation. The more technically focused they are, these wiki pages tend to become stale rather quickly. Whether outlining the broader system architecture, call patterns, the configurability of a component, take your pick! The facts change and it's a coin flip whether the document gets updated. If the change occurs while fixing a P1 it's several coin flips as to whether the documentation gets updated.&lt;/p&gt;
&lt;p&gt;Documentation is necessary though, and I don't want to convey that I write &lt;em&gt;no&lt;/em&gt; documentation. On the contrary, I just don't write documentation regarding those things. Rather, I find the best documents are those about static facts which don't need updating as time goes on. My prime example is decisions - what was decided and why was it? Historical snapshots which hold long-lived knowledge about a system. Long after I'm gone and Vendor A is replaced by Vendor B, it's still going to be true that we picked Vendor A at one point, and for some reasons. Some of those reasons might persist through to the Vendor B days, and this knowledge is as valuable as gold.&lt;/p&gt;
&lt;p&gt;For an administrator dealing with a malfunctioning system, stale technical documentation is not just unhelpful but entirely detrimental. However, being able to scour through &amp;quot;we did X because Y&amp;quot; gives the kind of knowledge helpful in a debugging context. Such documentation, properly done, should list the facts of the system at the time the decision was considered, the thinking that went into the decision incorporating those facts, and the specific decision or action that was actually taken. In future, I can always compare the facts then to the facts now, and I can always consider whether the reasoning would still be sound given the context.&lt;/p&gt;
&lt;p&gt;I've touched on this idea before in my writing, and perhaps it deserves its own treatment, but it will suffice here to just offer this advice. Document with great frequency, and treat any point where your writing is recorded (PR descriptions and comments, meeting minutes, kanban card comments) as opportunities for documentation for posterity. Take every documentation opportunity to state the relevant facts at the time, what the thinking behind a decision was, and never forget to write out what the specific decision was.&lt;/p&gt;
&lt;h2&gt;It's All Culture&lt;/h2&gt;
&lt;p&gt;So we've done all of the above. Our services are appropriately decoupled, each with great observability and a short turnaround time to deploy to prod. We've spread the knowledge around the team and our decisions are all documented in an easy-to-search manner. Yes, I can hear you laughing at that last one, but just suppose we've done that - what's it going to look like when it really hits the fan?&lt;/p&gt;
&lt;p&gt;Maybe you'll be able to effectively triage and address the issue, maybe not. This hammers home the people element - how deeply have you integrated this all into your culture? There's always a relationship between our proess and culture, as well as our technology and culture; it's always that both are dictated more by culture than anything. I've seen it happen too many times that we sit down to define a process only for it to fall apart - if you want to change the process you need to change the culture. &lt;code&gt;process = f(culture)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Open documentation and knowledge sharing, avoiding silos and planning for risk, redundancy, and succession need to be the fundamental ways that your firm operates. It's not good enough for a single team either! Your software (probably) doesn't exist because your team formed itself from the aether - on the contrary, a bunch of stuffy business folks got some idea or another and here you are. Your architecture is dictated by their requirements, and their cultural influence is just as important - maybe more important - than your own.&lt;/p&gt;
&lt;p&gt;Remember that these stuffy business folks (okay, maybe they're not &lt;em&gt;all&lt;/em&gt; stuffy) are administrators too sometimes, and they've probably got a &lt;em&gt;very&lt;/em&gt; different understanding of things than your senior performance engineer. Well, they both probably agree that they want the whole thing to be fast. Point being: these considerations all become circular in a way, each pointing to the next.&lt;/p&gt;
&lt;p&gt;In a way, this is the most important lesson for developing distributed systems. Maybe it's the most important lesson in developing software. Focus on people first, most things will fall into place if you get that right. In my work I'm reminded almost daily of a great Joe Strummer quote: &amp;quot;Without people you're nothing.&amp;quot;&lt;/p&gt;
</description>
      <pubDate>Fri, 23 Aug 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-08-23T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">the_art_of_hype_driven_development</guid>
      <link>https://ian.wold.guru/Posts/the_art_of_hype_driven_development.html</link>
      <title>The Art of Hype-Driven Development</title>
      <description>&lt;p&gt;From microservices to blockchain, from the dotcom bubble to AI, our industry has a surprisingly high hype cycle density. Many of us find this distasteful. However, as the old saying goes: if you can't beat them, join them; in this spirit I've developed the following principles to guide any aspiring grifters through the hypeverse. If you grind hard enough at conferences and social media - reputation be damned - you too can earn a massive profit for someone in Silicon Valley!&lt;/p&gt;
&lt;h2&gt;1. Follow the Money&lt;/h2&gt;
&lt;p&gt;If someone else is profiting massively over some new technology, do what you can to increase their profit. If money is the smart bet, then the biggest single pile of cash is the smartest bet. Since the best returns are &lt;em&gt;always&lt;/em&gt; just five years away, forget present-day realities; focus all your attention on imagining the glorious future of this new technology.&lt;/p&gt;
&lt;h2&gt;2. Confidence over Comprehension&lt;/h2&gt;
&lt;p&gt;Historically speaking, the more revolutionary an idea is, the less it is generally understood. Thus, the most revolutionary things don't need to be understood, and trying to articulate them is a waste of time. Just be confident, and be sure to have a promise to counter every uncertainty. Remember that specific statements are often false statements, so stick to more truthful statements like generalisms and benign platitudes.&lt;/p&gt;
&lt;h2&gt;3. Ruthless Simplicity&lt;/h2&gt;
&lt;p&gt;Always simplify, then simplify some more, and don't stop. Don't write a paper when a sentence will suffice, and then turn that sentence into a single word - one with a good &lt;em&gt;buzz&lt;/em&gt; to it. Don't release a complete product when an MVP will suffice, and then don't code that because you remember that the best MVP is a Minimum Viable PowerPoint. Use your buzzy word on that PowerPoint &lt;em&gt;a lot&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;4. Minimize Risks&lt;/h2&gt;
&lt;p&gt;The best way to minimize risks is to not have risks, and the best way to not have risks is to have &lt;em&gt;opportunities&lt;/em&gt; instead. Your goal is to have zero risks - yes, zero! Pivot every risk into a future opportunity. Simplify your explanation into a word with &lt;em&gt;lots&lt;/em&gt; of buzz, and put this word on your PowerPoint.&lt;/p&gt;
&lt;h2&gt;5. Organize your Community&lt;/h2&gt;
&lt;p&gt;If all else fails, you can always try starting a cult! The AI hypers have &lt;a href="https://www.youtube.com/watch?v=m75SAPSrDjc"&gt;had to resort to this tactic&lt;/a&gt; but it's been very successful for them.&lt;/p&gt;
</description>
      <pubDate>Wed, 22 Jan 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-01-22T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">the_case_for_single_reviewer_prs</guid>
      <link>https://ian.wold.guru/Posts/the_case_for_single_reviewer_prs.html</link>
      <title>The Case for Single-Reviewer PRs</title>
      <description>&lt;p&gt;As with all things, there are a lot of different practices our teams might adopt around pull requests (which I prefer to call &amp;quot;peer reviews&amp;quot;, but we can agree on &amp;quot;PRs&amp;quot;). Different teams in different environments or circumstances will certainly benefit from different processes. If you've suddenly inherited a huge legacy codebase and you need features &lt;em&gt;now&lt;/em&gt; then you're going to have a different process to a team that's cooking up large batches of copypasta microservices because your startup just got too much funding and decided to justify it by blowing money on AWS.&lt;/p&gt;
&lt;p&gt;The majority of teams doing actual development in the real world (the percentage of teams this describes is inversely correlated with the amount of VC money available, I assume) don't need a whole lot of process. Less is more, really. There's one process that I prefer as a baseline for most teams, light enough to be able to flex when it needs but still providing comprehensive benefits.&lt;/p&gt;
&lt;p&gt;This is the single-reviewer PR, where each engineer on a team assigns each of their PRs to a single other engineer on the team on a rotating basis per PR, such that each member of a team has an even number of PRs from each other member throughout the course of development.&lt;/p&gt;
&lt;p&gt;Maybe you've tried some form of this before and it didn't work out, or maybe you've never tried it before and it doesn't sound quite right to you. Maybe you like this, but you're trying to articulate the benefits of this scheme to a skeptic on your own team. I'm going to try to show the benefits of this setup and extra considerations you might need to make, and I'm going to do my darndest to not say &amp;quot;oh, you just didn't do it right!&amp;quot;&lt;/p&gt;
&lt;h1&gt;No Review Jam&lt;/h1&gt;
&lt;p&gt;Having multiple reviewers on a PR can lead to a situation where each reviewer implicitly defers to each other reviewer. &amp;quot;There's 6 reviewers on this PR, surely &lt;em&gt;someone&lt;/em&gt; will get to it!&amp;quot; This makes PRs take longer to be reviewed, or I have to constantly pester my peers for a review. I might need to constantly field questions like &amp;quot;Am I &lt;em&gt;really&lt;/em&gt; the right one to review the PR?&amp;quot;&lt;/p&gt;
&lt;p&gt;If the process requires multiple approvals, or approvals from specific people, has the problem of creating bottlenecks. These are particularly insidious; I've seen such processees implemented before to the effect that PRs would take multiple days on average to merge.&lt;/p&gt;
&lt;p&gt;Having a single reviewer eliminates all these issues. It's a level set with the team - you're going to get PRs assigned to you and you need to review them then. There's no deferring to other people, and if you're a teammember who consistently fails to find time to review PRs then we have an opportunity for a conversation.&lt;/p&gt;
&lt;p&gt;In typical fashion, I would keep a running tally in my head of the next engineer who'd be picking up my next PR, but there are advantages in massaging that order now and then. I worked on one team with a member in Frankfurt, and if I had an early evening PR in I knew I could assign it to him and have a review by the next morning.&lt;/p&gt;
&lt;h1&gt;Ultimate Knowledge Distribution&lt;/h1&gt;
&lt;p&gt;Most teams do, I think, value distributing knowledge across each of its members. This is such a significant consideration that &lt;a href="https://www.youtube.com/watch?v=BSaAMQVq01E"&gt;Uncle Bob suggests it be part of our code of ethics&lt;/a&gt;. Not only is the single-reviewer PR the best &lt;em&gt;PR process&lt;/em&gt; to encourage knowledge distribution, it might be the best single &lt;em&gt;process&lt;/em&gt; to do so.&lt;/p&gt;
&lt;p&gt;Each member of the team - from the junior out of college to the principal who's been there just a &lt;em&gt;bit&lt;/em&gt; too long - is going to be reviewing the full breadth of work that the team produces. Of particular importance to those lower on the ladder, the juniors will be reviewing PRs from the principals.&lt;/p&gt;
&lt;p&gt;It becomes everyone's professional responsiblity to take the time to understand what's being engineered. While you'd expect a more senior engineer to be able to provide a good review for a more junior engineer, they're advantaged by being required to consider the full breadth of work. The junior though is particularly assisted by needing to consider sometimes quite advanced code. There's no hiding, you've got to learn it.&lt;/p&gt;
&lt;p&gt;Now, we certainly wouldn't consider it good to leave any engineer alone with a bit of code they don't understand and ask them to sign their name to it. These cases give us the extra advantage of promoting communication. When I get assigned a PR that doesn't make a lot of sense to me, I want to have two video calls at least (assuming we're remote): one with the author to walk through the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; of the PR, and another call with a second engineer who understands the code better to answer any more technical questions. You might adopt something else, but this gives me great contact with the team. I especially recommend this for juniors to maximize exposure to others' knowledge.&lt;/p&gt;
&lt;p&gt;Here's the most fun I have with this process: when someone is about to open a PR in an area that one member of the team knows a lot about, I will suggest that they ask for a review from anyone &lt;em&gt;but&lt;/em&gt; that engineer. Indeed, the reviewer will (probably) need to reach out to that knowledgeable engineer in order to complete the review. This is, to me, the easiest way to achieve this sort of knowledge distribution.&lt;/p&gt;
&lt;h1&gt;Quality Reviews&lt;/h1&gt;
&lt;p&gt;If you haven't worked this way before, it might not seem intuitively obvious that it produces, I think, the highest quality reviews on average. I think the inclination would be that by distributing PRs around, you'd get quality reviews from those more experienced but not necessarily from others.&lt;/p&gt;
&lt;p&gt;Instead, I find that the review-ability of everyone on the team increases with this scheme. The key is it's not just that &lt;em&gt;you&lt;/em&gt; are receiving reviews equally from each member of the team, but that &lt;em&gt;they&lt;/em&gt; are receiving reviews equally as well. After a few cycles of reviews, it becomes clear who is good at giving reviews and who isn't. Behavior tends to normalize across the team with more contact between its members, and team members are biased towards adopting the practices of the quality reviewers.&lt;/p&gt;
&lt;p&gt;This, as with all things in software, requires communication, but the advantage is that the communication between members of the team is &lt;em&gt;at the minimum&lt;/em&gt; required through this PR system. Indeed, it naturally creates more communication between members on average. This is the key that causes quality reviewership to spread across the team. And again, if there's one member who is consistently failing to pick up what's being put down, there's an excellent opportunity for a conversation.&lt;/p&gt;
&lt;h1&gt;Transparency and Trust&lt;/h1&gt;
&lt;p&gt;The natural outcome of increasing both knowledge distribution and communication across the team is increasing transparency. Team members are more aware of what's happening in the code, what features and bugs are being tackled, and even just how their peers are feeling. Sometimes it's the small things that we don't pay enough attention to.&lt;/p&gt;
&lt;p&gt;Our tooling nowadays usually boast excellent automation features. One that I particularly like is when a distributed team uses one software for communication (Teams, Slack, etc.) you can set up an automation from GitHub to copy a conversation between a PR in GitHub and a thread on your communication platform, ensuring that your PR conversations are all recorded in the same place all of your other conversations are. This makes it incredibly easy for others to follow along, or to go back over previous conversations and decisions at a later date.&lt;/p&gt;
&lt;p&gt;Now here's one disadvantage you might be imagining. Wouldn't members of the team who &lt;em&gt;feel&lt;/em&gt; a particular attachment to some part of the system interject themselves here? Indeed, I have seen this happen, but it subsides over time. Because this process encourages knowledge distribution and accountability across the entire team, I find that members trust each other much more with this process, and tend not to interfere. Indeed, interference should be discouraged as in the PR the author and reviewer are engaged in a sort of mini pair programming session; their decisions are professional and trustable.&lt;/p&gt;
&lt;p&gt;More often than not - in fact almost always - when I see the situation that one member feel a knee jerk need to intervene, it's a learning opportunity for them. Frequently the author and reviewer are engaged with a new problem which alters some fundamental part of the original code. Sometimes the documentation is entirely insufficient leading to maybe more of a reach in th code than what's strictly required. These are not cases which require interference, they're cases where the original engineer can stand to learn - maybe to learn from a mistake they made or to learn from the wisdom of their peers. And, yes, sometimes it's a principal engineer learning from the wisom of two junior engineers struggling to comprehend the demon-speak of some terrible regex. Shame on you for checking in that regex in the first place!&lt;/p&gt;
&lt;p&gt;In reality though, these situations are rare even though they're incredibly beneficial (if for unexpected reasons). The knowledge distribution naturally causes a &lt;em&gt;work&lt;/em&gt; distribution, and paired with the increased trust across the team means that a codebase will, over time, have fewer and fewer landmines of particular engineers' passions. It encourages us to keep attachments at a professional distance, and allows the sort of iterations necessary across the entire codebase.&lt;/p&gt;
</description>
      <pubDate>Sat, 13 Jul 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-07-13T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">the_modular_monolith_wont_save_you</guid>
      <link>https://ian.wold.guru/Posts/the_modular_monolith_wont_save_you.html</link>
      <title>The Modular Monolith Won't Save You</title>
      <description>&lt;p&gt;It seems like it was just the other day that microservices were the cool thing to be doing, but as fads come and go the microservice fad has waned. What are the evangelists to evangelize now? Some have dipped their toes into the &amp;quot;modular monolith,&amp;quot; which at times has both pleased and confused me.&lt;/p&gt;
&lt;p&gt;I've been pleased that conversation has shifted to the benefits of monolithic codebases, although it would be nicer yet to replace this with conversation about the realistic drawbacks of distributed systems. I would think that monolithic code would be the default, that we wouldn't dream of distributing a system unless it developed to a point where some clear, specific principles would force the question of distributing. Maybe I'm an idealist.&lt;/p&gt;
&lt;p&gt;That leads partially to my confusion over the new focus on the modular monolith. &amp;quot;Modular?&amp;quot; Isn't that just how monoliths - nay, any professional code - should be written? Can't it just be the &lt;em&gt;default&lt;/em&gt; that we do our best to architect and engineer a &lt;em&gt;really good&lt;/em&gt; bit of code, and that code all executed together until it absolutely can't? Oh yes, I'm definitely an idealist.&lt;/p&gt;
&lt;p&gt;I fear that the modular monolith might take the same place that microservices had in the 2010s, with some degree of dogma, some degree of specific aesthetic concerns, and some degree of very vague rationale in their favor. Realistically, the microservices vs. modular monolith question is a false dichotomy, and this allows me to discuss the broader role of patterns and principles. Or, maybe I'm swapping out my idealism for skepticism.&lt;/p&gt;
&lt;h1&gt;Microservices vs. Modular Monoliths&lt;/h1&gt;
&lt;p&gt;In one sense these are natural opposites - many of those generally opposed to the proliferation of the microservices pattern holding the new modular monolith pattern up as a reaction. In any other sense these are two of an infinite number of ways to think about the organization of software.&lt;/p&gt;
&lt;h2&gt;The Rise and Fall of Microservices&lt;/h2&gt;
&lt;p&gt;Much has been written about this subject, some of which by myself on this blog, so I'll stay brief here. I can't recommend enough &lt;a href="https://renegadeotter.com/2023/09/10/death-by-a-thousand-microservices.html"&gt;Death by a Thousand Microservices by Renegade Otter&lt;/a&gt; as a primer. &amp;quot;Microservices&amp;quot; is a term that loosely refers to a distributed system composed of dozens to hundreds of very-small independently-deployed services, each focusing on a narrow and specific domain problem. Ideally each one wholly owns its domain area and contains its own persistence.&lt;/p&gt;
&lt;p&gt;The pattern arose at firms like Amazon and, maybe most famously, Netflix, as they attempted to tackle the most difficult distributed problems at a global scale. These firms didn't adopt this architecture for fun, nor did they do so out of an idea that it is &lt;em&gt;the&lt;/em&gt; right way to architect software (or even specifically distributed software). Rather, they adoped the architecture because they had to - there was no other way to solve planet-scale problems as theirs any other way.&lt;/p&gt;
&lt;p&gt;Being good colleagues, the architects and engineers involved with these firms published their results for the rest of us to admire and maybe learn from. Being bad colleagues, the tech evangelists observing this figured they could gain a lot of money or prominence by &amp;quot;helping&amp;quot; much smaller firms and teams with much smaller problems adopt this architecture. Microservices were presented as the antidote to all the crappy legacy code we had to work with up to that point. Code which, coincidentally, was monolithic. &amp;quot;Monolith&amp;quot; was made to mean &amp;quot;legacy&amp;quot; and &amp;quot;spaghetti&amp;quot;, and &amp;quot;microservices&amp;quot; were made to mean the opposite.&lt;/p&gt;
&lt;p&gt;Of course, the nicest thing to say about that line of thinking is it's misguided; bad code is bad code, but bad code that is &lt;em&gt;distributed&lt;/em&gt; is more expensive. In the disarray of the hype, cloud providers have made a fortune, and each year now there's an increasing amount of crappy legacy &lt;em&gt;distributed&lt;/em&gt; code that is so much more expensive to maintain than its previous generation of crappy legacy &lt;em&gt;monolithic&lt;/em&gt; code. The architectures are neither the problem nor the solution; it's the code.&lt;/p&gt;
&lt;p&gt;That was all as unsustainable as it sounds, and I feel confident now in saying that the microservices bubble has most certainly burst. The problem of course is that distributed systems are already hard - &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;really hard&lt;/a&gt; - and they'll bite a team not knowing what it's doing. If pain isn't felt setting up a new, distributed system, it's certain to be felt down the line when some new business requirement &lt;a href="https://www.youtube.com/watch?v=y8OnoxKotPQ"&gt;reveals the system has been coded into a corner&lt;/a&gt;. The pendulum of excitement has been swinging in a distinctly new direction over the last couple of years.&lt;/p&gt;
&lt;h2&gt;The Rise of the Modular Monolith&lt;/h2&gt;
&lt;p&gt;I'm not very good at history, so I'm not sure when this term came about. Maybe it's been around for 20 years, maybe it came up a few years ago when I first heard it. Whatever the case, it's maybe the most commonly-cited anti-microservices pattern. &lt;em&gt;Why&lt;/em&gt; that is, I'm unclear. I wonder if it's not partially a bit of clever marketing - microservices having been somewhat of a reaction to the perceived weakness of monolithic software, it makes sense that advocating for the monolith requires some answer to that perceived weakness.&lt;/p&gt;
&lt;p&gt;However the case, &amp;quot;modular monolith&amp;quot; comes about and is now excitedly and widely discussed. Loosely it means a monolithic architecture that has good separation between domain boundaries. But wait, I ask: isn't that just how software is always supposed to be made - with proper separation of concerns? If this is all the &amp;quot;modular monolith&amp;quot; is, then why aren't we just saying &amp;quot;do a monolith but don't suck at coding?&amp;quot; I might prefer a step further even: if monolithic architectures should be the default then maybe we should say &amp;quot;don't suck at coding and don't distribute unless you really, really need to.&amp;quot;&lt;/p&gt;
&lt;p&gt;But that hasn't got a good ring to it so now we've got the &amp;quot;modular monolith,&amp;quot; almost as though before microservices there were no well-written monoliths. And here's exactly the problem I perceive: because it needs some overarching explanation of the right way to structure software, it becomes a term that is open to being coopted into meaning something dogmatic. &amp;quot;Microservice&amp;quot; started as something incredibly different to how it ended up largely due to the widespread adoption of ill-formed ideas as to why software is the way it is.&lt;/p&gt;
&lt;h2&gt;Which One is Better?&lt;/h2&gt;
&lt;p&gt;I'm not going to answer that. Not just because I don't want to, but because I can't. Well, also because I don't want to. Microservices are better for Amazon and Netflix and the like. If you're starting up a brand-new system then a monolithic architecture is probably better. This is a false dichotomy though: these are not the only two options! In fact, you might adopt different definitions of &amp;quot;monolith&amp;quot;; consider whether you think a C# server with a standalone React frontend and a Postgres database is a monolith or a distributed system. Most systems today require &lt;em&gt;some&lt;/em&gt; kind of distribution, but it's also the case that &lt;em&gt;almost&lt;/em&gt; every single microservices system doesn't need to have a microservices architecture.&lt;/p&gt;
&lt;p&gt;I do feel comfortable committing to the position that monolithic architectures are the proper default for new systems development, but even then I need to attach the boring caveat that &amp;quot;default&amp;quot; practices are overridable by well-considered reasoning and understanding of the system's domain. This lands me back at a more fundamental truth though: naïveté is the highest virtue when approaching a new system. Well, naïve in a smart sense, I suppose. Keep it simple; less is more; start with what you need then grow from there; so on and so forth.&lt;/p&gt;
&lt;p&gt;If you're with me on that, then you'll probably agree it's a point in favor of the so-called modular monolith. &amp;quot;monolith more simple than distribute&amp;quot; + &amp;quot;more bias towards simple&amp;quot; = &amp;quot;more bias towards monolith than distribute&amp;quot;. And look how cleverly I can continue to avoid taking a definite position! That's not due to a commitment towards noncommitment on my part; you simply &lt;em&gt;can't&lt;/em&gt; make objective statements about the quality of any pattern, their goodnesses can only be judged in relation to some system, problem, or domain.&lt;/p&gt;
&lt;p&gt;There are plenty more foundational principles on which I feel quite comfortable taking objective positions, though. In addition to the simplicity bias, I think it's easy to say that code which is written and formatted neatly and tidily is better. At least, more professional. Code with the right balance of abstraction and modularity is better, and even that is quite a bit more definite than our architectural patterns: less is more but it grows with the scale of the domain, system, and complexity.&lt;/p&gt;
&lt;p&gt;These principles all seem to be pointing in favor of our modular monolith pattern, but taking that step from principles to an architectural pattern that may well pan out to be some new buzzword (just like what happened to &amp;quot;microservices&amp;quot;) gives me pause. Let me re-ask a question I hinted at earlier in this article: Does the &amp;quot;modular monolith&amp;quot; &lt;em&gt;just&lt;/em&gt; refer to &amp;quot;proper, professional coding practices also it's a monolith&amp;quot;, or is it instead an actual architectural pattern? If it's the former it's not a pattern; &amp;quot;try to code good&amp;quot; is a universal ideal.&lt;/p&gt;
&lt;h1&gt;Patterns Won't Save You&lt;/h1&gt;
&lt;p&gt;Increasingly I get the sense that the &amp;quot;modular monolith&amp;quot; term is being defined as something of a fad pattern. Some patterns are quite well-defined, even broad ones: if I ask you to implement dependency injection you can get to work and your result is probably not going to spark a lot of debate as to whether your code &amp;quot;is&amp;quot; or &amp;quot;is not&amp;quot; a proper example. Other patterns are poorly defined: I've never seen &amp;quot;single responsibility pattern&amp;quot; mentioned during a code review that &lt;em&gt;didn't&lt;/em&gt; spiral into debate.&lt;/p&gt;
&lt;p&gt;Fad patterns, particularly the architectural ones, are in a weird state as regards quality of definition. We can make some definite statements about them: microservices should have lots of distributed services, blockchain systems are slow, and so on. At the same time there is a general confusion, or at least lack of consensus: implementation details get fuzzy and lots of ideologues have divergent prescriptions for different problems that arise. They have a general, agreed &lt;em&gt;thrust&lt;/em&gt; that might be more aesthetic than technical, along with a large cloud of nebulous hype and half-thought-out implementation details. The microservices debacle shows that even with a concerted educational effort by our colleagues who actually know what they're doing, the nebula still wreaks havoc over plenty of systems.&lt;/p&gt;
&lt;h2&gt;The Role of Patterns&lt;/h2&gt;
&lt;p&gt;We certainly shouldn't understand architectural patterns as being too wishy-washy to be useful. Yes, they don't have definite prescriptions regarding implementation, but architecture sits around the implementation details a lot of the time anyway. These sorts of patterns give us a common language at a 10,000-foot view. For all the folly, if in a conversation I say &amp;quot;microservices&amp;quot; my interlocutor will get a good sense that the following conversation is going to involve topics of distribution, infrastructure, domain separation, and a lot of complaining about patterns!&lt;/p&gt;
&lt;p&gt;Patterns are good pedagogical tools too. In spite of the fact that university educations are typically poor preparations for professional software engineers, any serious course needs some way to give a name to different concepts discussed. Students need easy ways to be able to distinguish between broad concepts, and patterns give that sort of naming foundation.&lt;/p&gt;
&lt;p&gt;Similarly, patterns are useful for those of us who have been in the industry for a bit of time. Seeing as things change so frequently as they do, I don't only need some way to be able to latch on to new concepts, but also a way to apply those new concepts in my considerations at work. Patterns define a space where we can explore exercises in abstract thinking about problems, which is crucial to being able to integrate new thinking into our work.&lt;/p&gt;
&lt;p&gt;These (and maybe a couple others I've missed) constitute the proper utility of architectural patterns. None of them have anything to do with the implementation of systems, as none of them are capable of describing an implemented system. They're abstract and exist for the benefit of our thinking and communication, not for the benefit of our fingers-on-keyboards work. These patterns are informed by real-world problems, and in turn real-world solutions are informed by them, but &amp;quot;informed&amp;quot; doesn't do any heavy lifting for us.&lt;/p&gt;
&lt;h2&gt;The Real World&lt;/h2&gt;
&lt;p&gt;The real world is a world that has a lot of variables in it. When we're considering how to implement a real system we've got (off the top of my head) business requirements, quality attributes, specific infrastructures or cloud providers, performance targets, security of both the system and the environment it'll be working in, the hopes and dreams of managers, particular egos within a team, competitions with other deliverables for the firm, technical complexity, and the degree of understanding of the domain or solution. Surely, there's dozens more.&lt;/p&gt;
&lt;p&gt;These variables all present requirements your solution may need to fill, and don't roll your eyes at &amp;quot;egos within a team!&amp;quot; I'm quite serious about that one - keeping up development at a sustainable pace means keeping everyone happy. We're engaged from 8 to 5 in doing implementation, so details about those implementations affect our contentedness. Egos are an often-overlooked variable. At the same time though don't necessarily take it as being on par with &amp;quot;business requirements&amp;quot; - &lt;em&gt;that&lt;/em&gt; is a huge hole itself filled with many variables.&lt;/p&gt;
&lt;p&gt;The broader point is that the patterns account for a small set of requirement variables and propose a solution to satisfy &lt;em&gt;those&lt;/em&gt;. Variables which aren't accounted for will alter the implementation of the pattern or maybe make it irrelevant. Maybe your business requirements &lt;em&gt;do&lt;/em&gt; contain the set of factors that are addressed by some pattern, but also include an extra one which makes the pattern unsuitable.&lt;/p&gt;
&lt;p&gt;There's no formula to apply here, and there's (as best I can tell) no way to teach how to navigate discovering a proper implementation for a set of requirements. There's only experience and diligence, and constant learning. Our patterns only help us with that third point. If you take from patterns the principles of problem solving, then you can solve any problem with the same &lt;em&gt;principles&lt;/em&gt;. However, if you only satisfy requirements by applying predefined patterns, you've missed the forest for the trees.&lt;/p&gt;
&lt;h2&gt;Avoid the Trap&lt;/h2&gt;
&lt;p&gt;With this understanding, let me ask: how many of the ill effects of microservices implementations are ultimately attributable to a misunderstanding of the roles of patterns? How frequently has it been that some demonstration of microservices at a pattern level has found its way into our codebases? The problem might not be the pattern, it might be &lt;em&gt;us&lt;/em&gt;. Both the &amp;quot;us&amp;quot; that becomes lazy when considering implementing a real system, and also the &amp;quot;us&amp;quot; that becomes lazy when teaching or mentoring. These are two sides of the same coin: the misunderstanding of the role of patterns gives way to a confusion of the pattern for an implementation prescription. This is the &amp;quot;pattern trap.&amp;quot;&lt;/p&gt;
&lt;p&gt;If the recent buzz around the modular monolith resembles the same follies as that around the microservice, are we falling into the same trap again? If I wanted to be cynical I might throw my arms up in despair that our industry is eternally captive to hype. There's plenty of indication that a similar thing is happening - popular teachers are lazily making prescriptions from a loose understanding of a pattern and we're all not immune from a bout of laziness while inflicting those prescriptions upon our own code.&lt;/p&gt;
&lt;p&gt;I'll go back to the most hilarious aspect of this all (at least, hilarious to me): I still don't think that &amp;quot;modular monolith&amp;quot; means anything! Take a gander over to &lt;a href="https://en.wikipedia.org/wiki/Modular_programming"&gt;Wikipedia on 'modular programming'&lt;/a&gt; - &amp;quot;modular&amp;quot; isn't some ultra-sophisticated way of handling domain separation; it's just using classes. Or packages, or &amp;quot;namespaces&amp;quot;, or whatever your language supports. You're already doing it! Monoliths &lt;em&gt;are&lt;/em&gt; &amp;quot;modular.&amp;quot; What gives?&lt;/p&gt;
&lt;p&gt;This has potentially alarming implications. A survey of articles about the &amp;quot;modular monolith&amp;quot; offer a wide array of interpretations about what work &amp;quot;modular&amp;quot; is doing in that name, many of which incompatible with each other. One writer might take it to mean using &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design"&gt;DDD&lt;/a&gt; and another might take it to mean writing each module in a way that it might easily be separated out into a distributed service. These are wildly different.&lt;/p&gt;
&lt;p&gt;One of the main problems with &amp;quot;microservices&amp;quot; was that the term came to mean less and less over time. It started as a very specific pattern describing architecture and practice, and over the years evangelists watered down and confused the meaning to the point that the popular understanding of the term became something like &amp;quot;vaguely distributed system and also the word 'event' should probably be used.&amp;quot; The end result was to create an environment for a hefty set of poorly-considered and many times contradictory prescriptions to take hold. These became implemented on enough codebases that the popular zeitgeist now has a bad taste for the word &amp;quot;monolith.&amp;quot;&lt;/p&gt;
&lt;p&gt;And to think - &amp;quot;modular monolith&amp;quot; has &lt;em&gt;started off&lt;/em&gt; not meaning anything! The pattern trap is a real trap, and trends tend to set these traps. There's plenty of new trends constantly about, and the modular monolith is one of them.&lt;/p&gt;
&lt;h1&gt;Principles over Patterns&lt;/h1&gt;
&lt;p&gt;None of this is to say that any pattern should be avoided, and that includes writing on both microservices and modular monoliths. The point is how we understand them, and how those come to inform our implementations. The role these patterns have is not one of prescribing solutions; they're intellectual exercises. Studying patterns will, hopefully, help one develop ways to consider problem solving in the face of different sorts of requirements. They're a &lt;em&gt;component&lt;/em&gt; of developing principles to guide our approach to writing software. Principles which, if you're doing things right, will change over time.&lt;/p&gt;
&lt;p&gt;The real requirements of our systems are sometimes contradictory even, and this frequently makes us have to choose least-worst options. Often, this even results in a contradiction of principles we hold. As a result, we engineers are frequently engaged in a process of reconciling these contradictions, and it's that process of working through contradictions that causes us to emerge with solutions to our problems, implementations of our systems, and hopefully new principles we can keep going forward.&lt;/p&gt;
&lt;p&gt;The systems we actually implement are synthesized from the requirements and contradictions of the multitude of real variables surrounding them. The &lt;em&gt;actual&lt;/em&gt; design of our systems will never follow 1-to-1 how microservice or modular monolith or any other pattern might describe. To implement a system we need to have the skills to be able to construct it based on the real requirements it has, not based on the ideal or ephemeral assumptions of the patterns.&lt;/p&gt;
</description>
      <pubDate>Wed, 19 Feb 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-02-19T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">the_network_is_reliable</guid>
      <link>https://ian.wold.guru/Posts/the_network_is_reliable.html</link>
      <title>The Network is Unreliable and Reliability is Scary</title>
      <description>&lt;p&gt;When the &lt;a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing"&gt;Fallacies of Distributed Computing&lt;/a&gt; were first written in the 90s, networks were unreliable. The internet was unreliable, intranets were unreliable, even radio was sometimes spotty back then. In the last thirty years, we as an industry have taken this unreliable infrastructure and ... left it there. Packet failures, client timeouts, and the occasional solar flare continue to be a problem not because of any inadequacy on our part but because it's a flaw which is inherent in the system; no network can ever guarantee reliability. Radio is still sometimes spotty because, just like the internet, sending any information over large physical distances is always going to have interruptions and loss. The first Fallacy of Distributed Computing is to assume the opposite of this - &lt;em&gt;the network is reliable&lt;/em&gt; - and it's first for a good reason: it really matters.&lt;/p&gt;
&lt;p&gt;Now, I wonder if in the last thirty years we haven't actually made this problem worse. In the 90s there weren't a lot of microservice systems making exorbitant use of load balancers, firewalls, gateway APIs, and the like. These are useful tools, but each new component in the distributed stack adds a point of failure. These systems can and do fail in their own right, but the incidence rate of network failures specifically will increase as more of these components are included.&lt;/p&gt;
&lt;p&gt;It seems then that we have a good candidate for a first fix: simplify the architecture! Does every microservice need its own firewall? Do we have multiple gateway APIs? A serious and focused audit of the system architecture can reduce a lot of layers in the distributed stack, and overall improve the reliability of the system - the most simple systems tend to be more reliable. However, this only gets us to a certain point - a lot of systems still require load balancing, and you're going to need a firewall &lt;em&gt;somewhere&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;Store and Forward (and Retry)&lt;/h1&gt;
&lt;p&gt;The simplest implementation to get some fault tolerance is to implement a retry when we see a network error after sending a request. This is &lt;a href="https://en.wikipedia.org/wiki/Store_and_forward"&gt;store and forward&lt;/a&gt;, but I prefer to call it &amp;quot;store/forward/retry&amp;quot; as these all tend to be related. Intermediate systems like gateway APIs and load balancers and the like might have simple implementations of this pattern themselves. To implement this, you'll need to &amp;quot;store&amp;quot; the message, send it (&amp;quot;forward&amp;quot;), and then you can retry on failures as needed by resending the stored message. This pattern works well because it's very simple and provides a good level of recovery from some network errors. It's best practice to implement some sort of retry system - if the packet is dropped en route from client to server, you'll want to be able to resend that.&lt;/p&gt;
&lt;h2&gt;Idempotency&lt;/h2&gt;
&lt;p&gt;There is a problem here though - it is possible for our client to get a network error even if the operation did succeed on the server: suppose the response packet was dropped or the client timed out before receiving the success response. That means that we will potentially send the same request to the server more than once, leading to potential error on the server from reprocessing the same valid, successful request more than once. This is solved by ensuring the operations on the server are &lt;a href="https://en.wikipedia.org/wiki/Idempotence"&gt;idempotent&lt;/a&gt; - that replaying the same request twice won't cause these sorts of errors. Indeed, idempotency should be a default for all operations in a distributed system.&lt;/p&gt;
&lt;p&gt;Here's a problem on top of that though: some operations cannot be idempotent. I'm currently working in ecommerce, which is rife with examples of single-fire operations: ship an item from an order, send an email when an item was bought from your gift registry, charge a credit card, and so on. In some cases we're able to architect the systems around these to make it so that the web requests are idempotent while the operation isn't. In other cases, we need to be a bit more clever.&lt;/p&gt;
&lt;p&gt;Here operation IDs can solve the majority of your problems - generating a UUID for each operation and storing which ones we've processed is a simple solution that works in many cases. That said, non-idempotent operations are sometimes dependent on message ordering in order to work properly. If you're not able to architect away from this, you probably need some more robust patterns. I'll touch on some of these throughout this article.&lt;/p&gt;
&lt;h2&gt;Boilerplate and Complexity&lt;/h2&gt;
&lt;p&gt;There's yet another problem with implementing store-and-forward everywhere - one that affects my day-to-day life much more annoyingly than misordering a customer's packets: now I'll have a bunch of boilerplate around my code! This is a bigger problem for some codebases and less a problem for others. If your application doesn't have a lot of requests and you only need a base level of resilience, then moving the boilerplate into a shared library, or consuming an existing third party library for this, can be sufficient.&lt;/p&gt;
&lt;p&gt;On the other hand, if you're setting up a distributed system of even a moderate size, you've probably got a fair amount of traffic going around, and it might make sense to set up a more comprehensive scheme. Some third party libraries do help out here, and allow a sharing of settings across components or provide more intricate solutions for orchestrating some of the request policies across a whole system. Still there are problems here - suppose the client application goes offline before it's able to resend its request, now I might need to add some persistence somewhere if the system requirements need that level of resiliency.&lt;/p&gt;
&lt;p&gt;A more powerful alternative to solve this problem is a &lt;a href="https://en.wikipedia.org/wiki/Message_queue"&gt;message queue&lt;/a&gt; (MQ). An MQ acts as a standalone message bus for a distributed system, allowing your clients to send their requests into the queue and letting the queue handle all of the considerations to make sure it gets to the client. Most of them offer robust UIs to give you a good level of insight and control over the system, and there are several options that are widely used for this purpose. Now your client needs very little logic in the way of sending a request - it just needs to make sure the request gets to the MQ.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/the_network_is_reliable_mq.png" alt="MQ" /&gt;&lt;/p&gt;
&lt;h1&gt;More Complicated Patterns&lt;/h1&gt;
&lt;p&gt;The very minimal amount of work that we must do when implementing communication across a distributed system - whether &amp;quot;distribution&amp;quot; in your context means a client and a server or a microservices cluster spanning the globe - is to implement redundancy against the fragile network. In my mind, this means that store/forward/retry and idempotency are the default. As our requirements scale and our systems inevitably become more complicated, these become insufficient either in that they can't satisfy the system requirements or that they don't appropriately guard us against the chaos of the network.&lt;/p&gt;
&lt;p&gt;There's another complication on top of this; there's &lt;em&gt;always&lt;/em&gt; another complication. Peter Bailis and Kyle Kingsbury, &lt;a href="https://cacm.acm.org/magazines/2014/9/177925-the-network-is-reliable/fulltext"&gt;writing for the ACM in 2014&lt;/a&gt; cited some of the only research I am aware of regarding the effects of network fragility on our systems and users. Their work produced this quote (a summary of one such citation), which, to paraphrase what the kids say these days, lives within me without payment of room or board:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Perhaps more concerning is [Microsoft's and the University of Toronto's] finding that network redundancy improves median traffic by only 43%; that is, network redundancy does not eliminate common causes of network failure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Can we go beyond redundancy and introduce some patterns that will 100% eliminate the effects of network failures? Well, we can introduce some patterns beyond redundancy but we'll never get to 100%.&lt;/p&gt;
&lt;h2&gt;Asynchronous Communication&lt;/h2&gt;
&lt;p&gt;TCP, UDP, and HTTP primarily support request-response type models: I send a request, I wait, and then I get a response. So far we've been considering how this doesn't work, so I think it's natural here to feel that this way of thinking has adopted, at least a little bit, of the fallacy &lt;em&gt;the network is reliable&lt;/em&gt;. If I can't rely on getting a response, or even that my request will reach its destination, or if I can't even guarantee that redundancy will be as helpful as I need, then it intuitively follows that relying exclusively on request-response isn't sufficient.&lt;/p&gt;
&lt;p&gt;I touched very briefly on the utility of MQs for distributed communication. Indeed, they're a great tool to be able to add a wealth of error handling (and other messaging-related) logic without polluting my codebase(s). I don't think that's the most attractive aspect of these systems though. While most MQs are quite good at supporting a request-response type messaging model, they're exceptional useful if we can move to a fire-and-forget model where I send the request and &lt;em&gt;know&lt;/em&gt; I won't get a response.&lt;/p&gt;
&lt;p&gt;But wait, my &lt;code&gt;OrderMicroservice&lt;/code&gt; needs to be able to get item data from the &lt;code&gt;ItemMicroservice&lt;/code&gt;! How do we do this without request-response? The answer in a microservice context is data duplication. The service which is responsible for manipulating the data publishes notifications on changes to that data, and systems which rely on this data will listen to those notifications and maintain their own copies of that data as they need. This is how asynchronous communication works in distributed systems: &lt;em&gt;notify&lt;/em&gt;, don't &lt;em&gt;request&lt;/em&gt;. This way of thinking changes our system a lot; indeed, it upends our entire architecture. Beyond the change to the communication pattern, it makes data-owning services only singularly responsible for &lt;em&gt;manipulating&lt;/em&gt; data, rather than being responsible for both &lt;em&gt;manipulation&lt;/em&gt; and &lt;em&gt;querying&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As different as it is from the &amp;quot;traditional&amp;quot; modus operandi, this has almost become the default communication scheme in distributed systems. Particularly systems with varied, geographically distributed components such as IoT and banking (yes, not everything is microservices yet!) It eliminates whole categories of errors, and in our case it helps to eliminate not just one area as a source of network errors (the response) but also supports other best-practice patterns that feed back to helping us maintain our rigidity against the network.&lt;/p&gt;
&lt;p&gt;There's tradeoffs though, as there are with all things. Asynchronously communicating systems have trouble reaching consensus, and a lot of times this can be entirely impossible. That's not a dealbreaker for a lot of systems, though it's a major one if your requirements necessitate it.&lt;/p&gt;
&lt;h2&gt;Outbox&lt;/h2&gt;
&lt;p&gt;One common point raised is that as more logic is added around outbound requests, the slower it is to handle those requests. In cases where my hot path is very hot and still needs to produce a fair number of outbound requests (as you might need to if you're notifying on all data change operations), I'll want to optimize my logic as much as I can. Perhaps it will seem attractive to not provide adequate robustness around my requests to make them faster.&lt;/p&gt;
&lt;p&gt;The obvious pattern to use here would be to shuffle your message off to a queue running in a background process that will eventually publish the message, just outside the thread the hotpath is on. This works in a lot of scenarios, but there are robustness concerns yet with this. A helpful pattern here is the &lt;a href="https://microservices.io/patterns/data/transactional-outbox.html"&gt;Outbox Pattern&lt;/a&gt;. The core concept is the same - we maintain a background process in a separate thread which handles sending messgaes with proper resiliency against the faulty network, however the enqueueing mechanism is the clever bit.&lt;/p&gt;
&lt;p&gt;This pattern suggests that your database should have a table, or tables, containing the messages which you want to enqueue - this is the &amp;quot;outbox&amp;quot; table. When your application makes the update to the business objects in the database, in the same transaction it would add the message to be sent to the outbox table. The background message sending process then listens to this table (either by polling or by having the database raise events) to perform the sends. This is clever because, while you do need to write the logic to insert the message into the table, you don't specifically need to call the message publishing service to enqueue the message. On top of this, that you're using your database as the queue gets you a persisted queue for free.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/the_network_is_reliable_outbox.png" alt="Outbox" /&gt;&lt;/p&gt;
&lt;p&gt;This pattern is worthwhile if you've got a &lt;em&gt;hot&lt;/em&gt; hot path, need the extra resiliency in your queue, and the extra cost of running the background process is worth it to you.&lt;/p&gt;
&lt;h2&gt;Event Sourcing&lt;/h2&gt;
&lt;p&gt;Some applications have a high need to preserve message ordering, usually because the state of the system is dependent on the temporal changes over the course of several events. These systems are good candidates for &lt;a href="https://www.eventstore.com/blog/what-is-event-sourcing"&gt;event sourcing&lt;/a&gt;, and this pattern can help us alleviate some of the pain of hardening our system against a faulty network.&lt;/p&gt;
&lt;p&gt;This pattern imposes (very broadly) that you should save all of the events which alter your state, and that the state should subsequently be &lt;em&gt;derived&lt;/em&gt; from these events. This is opposed to our traditional way of persisting data, where we process an event, update the state to reflect the changes specified by that event, and then forget the event. Event sourcing allows a number of benefits like being able to replay state, but what's interesting to us is that it allows inserting an event &lt;em&gt;in the middle&lt;/em&gt; of a set of events which have already been processed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/the_network_is_reliable_event_source.png" alt="Event Sourcing" /&gt;&lt;/p&gt;
&lt;p&gt;This is beneficial to us if message ordering is high on our considerations list. If we're implementing proper resiliency when messages are dropped on the wire, we're going to be retrying messages, and there's a fair chance we're going to be sending some messages out of order in this scenario. As long as our events are properly dated, they can be ordered appropriately (and change the state appropriately) in our eventually-consistent system. This pattern also has the power to transform some non-idempotent operations into idempotent ones - instead of changing the state directly in a non-idempotent way, we'd be inserting/upserting/updating the single event in an idempotent way.&lt;/p&gt;
&lt;p&gt;One word of warning though - event sourcing is a huge pain to implement and maintain. This pattern can very quickly get very complicated, and if you're careless then you can mangle data over time. Systems like &lt;a href="https://www.eventstore.com/"&gt;EventStoreDB&lt;/a&gt; or the &lt;a href="https://github.com/eugene-khyst/postgresql-event-sourcing"&gt;postgresql-event-sourcing plugin&lt;/a&gt; can help to make this easier, but that of course requires an investment in those systems. This is a pattern to study carefully and only use if it's appropriate for your use case.&lt;/p&gt;
&lt;h1&gt;Chosing the Right Solution&lt;/h1&gt;
&lt;p&gt;The trend which I think is plainly obvious here is that each next step we take in the battle against the unreliability of the network introduces greater and greater rippling effects across our code, infrastructure, and architecture. It's common advice for most any problem in distributed computing - not just this one - that the best solution is for us to upend just about everything we know about software engineering! Okay, I'm being a bit facetious, but as solid and well-proven as patterns like asynchronous communication are, they're quite different to other ways of writing software.&lt;/p&gt;
&lt;p&gt;Difference between domains isn't bad in itself. A CRUD API is never going to look anything like a desktop application, which is going to look entirely different itself from any video game code you might find. It's probably a sign of being on the right path that we find that radically different domains produce radically different styles of engineering and architecture. Distributed systems aren't a single, contiguous domain though: the architecture we consider for a client-server system is itself going to be radically different from a microservices cluster, and yet they're both &amp;quot;distributed&amp;quot;. Some patterns are going to work better or worse depending on the requirements of each different system.&lt;/p&gt;
&lt;h2&gt;Cost-Benefit&lt;/h2&gt;
&lt;p&gt;Extending the observation that the increasing steps exacerbate the complexity of our systems, we can say that the more reliable of a system you engineer, the more costly it is. I have no evidence for this, but it is maybe helpful to assume a sort of exponential or logarithmic growth in cost vs. reliability (I know &amp;quot;exponential&amp;quot; and &amp;quot;logarithmic&amp;quot; are very different but I slept through math class, passed by answering &amp;quot;C&amp;quot; on all the questions, and for my purposes they occupy the same category in my brain, right next to Snoopy and Woodstock). 100% reliability can never be achieved, so there is some point of reliability which becomes cost ineffective for your firm.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/the_network_is_reliable_asymptote.png" alt="Reliability vs Complexity" /&gt;&lt;/p&gt;
&lt;p&gt;I don't have an answer for this. I can bring up patterns with a pros and cons list all day - I've presented a couple here - but this comes down to a study by your team and firm for its use case. Start by engineering a redundant system and extend (or replace) the components and architecture where you need. Since we can't ever achieve 100% reliability, we'll need to throw in the rag at some point and accept that failure has to be an option, but remember that failure is only an option if we've explicitly coded for it.&lt;/p&gt;
&lt;h2&gt;How Do We Know Our Solutions Work?&lt;/h2&gt;
&lt;p&gt;How do we know we've engineered a system which is appropriately reliable given our domain? Here's one more quote from &lt;a href="https://cacm.acm.org/magazines/2014/9/177925-the-network-is-reliable/fulltext"&gt;that ACM paper&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Moreover, in this article, we have presented failure scenarios; we acknowledge it is much more difficult to demonstrate that network failures have not occurred!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed, you can't prove that something &lt;em&gt;didn't&lt;/em&gt; happen. With appropriate monitoring you can get an insight into how frequently network failures tend to occur, and you should track these and line them up with any cascading system failures. Generally, the absence of these indicators suggests strongly enough that there are no failures occurring.&lt;/p&gt;
&lt;p&gt;Using this data to feed back to know if you've over- or under-engineered your system is a different beast, though. This is part of the art of our field. Especially with the patterns we're talking about here, it's almost impossible to do A/B testing. I recommend a gradual approach: start with the lowest amount that you need, and if your monitoring indicates failures in a particular way, then address those in the minimally-invasive way and repeat.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The network is unreliable and it won't ever be reliable. I've presented some of the general patterns to consider here, but there's a wealth of ideas on this topic. The job of creating properly resilient systems is about balancing tradeoffs as your requirements demand. There are dozens of patterns out there for dealing with this problem in different domains, trading off one bit of reliability for performance or one style of communication for redundancy, or any other tradeoff you might make.&lt;/p&gt;
&lt;p&gt;Like the approach to the other fallacies, the right approach to this one (insofar as there is any &amp;quot;right&amp;quot; approach) is one of mindset more than code. The fallacies are so-named not because they're actual logical fallacies but because the point is to remind us it's a mindset problem. If we engineer our systems from the mindset that &lt;em&gt;the network is reliable&lt;/em&gt;, then we'll end up with a deficient system because it's not true. If we adopt the proper mindset that not only is the network unreliable but also that it's a difficult and domain-specific problem to solve, we'll end up engineering thoughtful and considered systems.&lt;/p&gt;
</description>
      <pubDate>Wed, 21 Feb 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-02-21T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">the_network_is_secure</guid>
      <link>https://ian.wold.guru/Posts/the_network_is_secure.html</link>
      <title>There's More to Network Security than the Network</title>
      <description>&lt;p&gt;As I'm reviewing and writing overviews of the fallacies of distributed computing, I'm trying to keep observations and suggestions at a 10,000-foot view: I want to describe each problem, related patterns, and convey the mindset behind the fallacy. Each fallacy is quite discrete, has obvious examples, and clear patterns that directly relate to it. The fourth fallacy, however, is a gigantic topic - &amp;quot;The Network is Secure&amp;quot; covers &lt;em&gt;all&lt;/em&gt; of network security. I wonder if it's misplaced in the fallacies then, as we could very easily imagine a distinct set of the Fallacies of Network Security.&lt;/p&gt;
&lt;p&gt;I'm not a security expert, I avoided the security class in university, and I typically find myself relying on colleagues for the more involved security considerations. That's unfortunate because security is so important and overlooked. One study &lt;a href="https://pages.cs.wisc.edu/%7Ezuyu/files/fallacies.pdf"&gt;cited by a University of Wisconsin paper&lt;/a&gt; suggests that 52% of networks are only defended at the perimeter. The link to that study no longer exists and I have a lot of questions about methodology and specific definitions, but I think we can all intuit that there's some truth here.&lt;/p&gt;
&lt;p&gt;I'm not a great authority, then, to recommend specific practices for mitigating security issues; indeed, I'll allow someone else to write a series on the &amp;quot;Fallacies of Network Security&amp;quot;. However, I can speak about the areas where security concerns come up, and why those areas are a problem. Just as well then, otherwise this article could be far too long! I hope I can convey the idea that even for those of us developing alongside a strong, security-minded colleague, we can adopt a holistic view of security issues and keep them in mind in the course of our work. I don't think I'm going to point out any new, groundbreaking information here, but it can be helpful to have these top-level ideas collated in one place.&lt;/p&gt;
&lt;h1&gt;Keep the Services Secure&lt;/h1&gt;
&lt;p&gt;The very baseline of any security is at the level of the services, and not the network itself as one might assume. This is the fallacy - the assumption that the whole network is secure leads us to develop more vulnerable services. We might develop services that are relaxed in their authorization or authentication, don't implement proper (or any) access control, or that don't properly validate user input.&lt;/p&gt;
&lt;p&gt;There's a lot to be said about authorization and authentication. &lt;em&gt;That&lt;/em&gt; you have auth in place is the first step, but there's a lot of considerations when it comes to the specifics. How you handle keys and passwords is important, and if you're relying on third party auth then the ways you go about securing your communication with them, and how you handle the keys you receive from them, matter. This is an important area of focus, for you and your team to thoroughly consider how these are all handled.&lt;/p&gt;
&lt;p&gt;Access control plays into this - &lt;em&gt;which&lt;/em&gt; users can access &lt;em&gt;what&lt;/em&gt; data? Ensure you're appropriately applying the principle of least privilege - users should only have as much access as they've been authed to have. This is important at the level of each individual service, being skeptical of the security of the whole network is the idea here. Even guarding against internal denial of service attacks by implementing rate limiting might not be a bad idea, depending on your situation.&lt;/p&gt;
&lt;p&gt;Even when I can be sure I'm correctly dealing with requests from a valid user, I still need to handle their inputs appropriately. This doesn't just end at sanitizing SQL inputs to guard against injection attacks - is your user able to input anything - say, regex - that can get around other data protections? Maybe your service's auth code is so complicated that it's easy for incorrect auth attributes to be associated with a request as it's processing and you accidentally give users higher privileges based on their own inputs. You might roll your eyes at that suggestion but I guarantee you it's less funny to encounter in the wild!&lt;/p&gt;
&lt;p&gt;To boot, you can get issues with poor configurations - the use of default passwords is one that I encounter with some regularity. Most systems should have guidelines on security practices as regards their configuration, and this shouldn't be overlooked. This also bleeds from the service itself to how the servie itself is configured when running; yes, those running your service should be following your practices, but you should set them up for success by creating a service which is difficult or impossible to misconfigure.&lt;/p&gt;
&lt;h1&gt;Keep the Data Secure&lt;/h1&gt;
&lt;p&gt;The next obvious security concern is data transmission. I hope you're using HTTPS for everything! HTTPS makes it easy to start securing this area but it's not the end. Particularly if you're handling sensitive data like credit card numbers, you're going to need extra protections in place.&lt;/p&gt;
&lt;p&gt;If you do have sensitive data, you probably want that data encrypted as it goes over the wire. Using proper, not-outdated encryption algorithms is important - there's still a fair amount of SHA-1 out there. Keeping up-to-date here is important too - algorithms and practices fall out of date like libraries, frameworks, or other practices.&lt;/p&gt;
&lt;p&gt;Data integrity can also be a concern depending on your circumstance. It's good practice generally to use a hash as a checksum for your data, not just for the security aspect but to ensure your packets are transmitting correctly. That's quite circumstantially dependent and up to you and your team to choose whether it's needed in your case, but if you're transmitting sensitive data encrypted anyway you probably want this guard as well.&lt;/p&gt;
&lt;p&gt;Access controls certainly help keep the data secure as well - if we appropriately guard who can receive which information then we've won half the battle. The regular suggestions for securing a network - firewall, maybe a VPN - help here too, but remember that if you're developing services within a network you can't rely on these being present or even helping you if they are. Security can't be assumed.&lt;/p&gt;
&lt;p&gt;Data security has an internal aspect, too - sensitive data should be masked if it's stored internally in logs or audits. Properly enforcing how sensitive information is handled on your team is quite important not just because you don't want a rogue employee misusing it with malicious intent, but you want everybody protected if any of your internal systems are compromised. This is a vigilance mindset which is important to develop and maintain whenever we're working on our systems - you don't want to be the one whose laptop was breached and exposed PII!&lt;/p&gt;
&lt;h1&gt;Guard Against Third Parties&lt;/h1&gt;
&lt;p&gt;In &lt;a href="https://ian.wold.guru/Posts/lateny_is_zero.html"&gt;my article on latency&lt;/a&gt; I pointed out that third parties can introduce latency onto us, and there's a similar relationship in a security sense - third parties can introduce vulnerabilities on us. This can happen whenever we have a connection with a third party, the obvious one being whichever third party services ours call into. A less obvious occasion for third-party-induced vulnerabilities that still effects everyone is when the building or delivery of our software is compromised - this process almost always involves external systems.&lt;/p&gt;
&lt;p&gt;I think the first, most obvious step to guarding against vulnerabilities from a third party system is to make sure the system and the company owning it was appropriately vetted before relying on them. Something like Azure seems to be a pretty safe bet that they're going to do, and continue to do, what they can on their end to keep vulnerabilities away from you. Other vendors might be more or less trustworthy, but the point then is that you probably shouldn't replace MailChimp with BobsSuperGreatEmailService after a thorough review.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; need to consume BobsSuperGreatEmailService though, hiding it behind an isolation layer would be a smart move. That can be said for any system security-wise. Does every external system need an isolation layer? Maybe, that's up to you and your team to determine. There's pros and cons here, and the point is that security should be one of the considerations when working through this.&lt;/p&gt;
&lt;p&gt;The supply chain vulnerabilities are the more interesting ones here. We all rely on third parties - sometimes a &lt;em&gt;lot&lt;/em&gt; of them - to build and deploy our systems. Every codebase I know brings in several libraries through a package manager, just like those libraries rely on others, and so on down the line. You can be dependent on dozens or hundreds of libraries, and each of them is a security concern. Including a library should be the result of an audit, and these need to be continuously reviewed to catch vulnerabilities if they occur.&lt;/p&gt;
&lt;p&gt;This might seem like a far-off case, but my friends in the .NET world will recall the recent incidence where &lt;a href="https://www.youtube.com/watch?v=A06nNjBKV7I"&gt;the maintainer of the most popular C# mocking library put malware in a release&lt;/a&gt;, harvesting emails from the machines of any developers who built their code with the latest version of his library. The most charitable reason to give why he did this is profound ignorance, and it shows that security vulnerabilities have nearly infinite vectors to exploit. I commend Nick Chapsas for having shared that video as soon as he did; I was able to raise that alarm at my firm that morning and over the next two weeks one of my colleagues painstakingly removed Moq from our main codebase. Indeed, incidents like this are largely reactive.&lt;/p&gt;
&lt;p&gt;Some of the most impactful and famous attacks have been supply chain attacks. The &lt;a href="https://www.cisecurity.org/solarwinds"&gt;SolarWinds attack&lt;/a&gt; was a compromise of their delivery system, and the &lt;a href="https://www.wired.com/story/inside-the-unnerving-supply-chain-attack-that-corrupted-ccleaner/#:%7E:text=The%20software%20updates%20users%20were%20downloading%20from%20CCleaner,distributed%20software%20is%20actually%20infected%20by%20malicious%20code."&gt;CCleaner attack&lt;/a&gt; saw legitimate versions of the product compromised with malicious code. While these third party vectors are some of the most obscure and difficult to guard against, these compromises can result in serious damage.&lt;/p&gt;
&lt;h1&gt;Guard the Whole Network Against Attacks&lt;/h1&gt;
&lt;p&gt;This seems like the easiest and first thing to do really - keep malicious actors outside my network of distributed components. As I keep mentioning though, this is the source of the fallacy! Indeed, we &lt;em&gt;do&lt;/em&gt; have to do what we can to secure the network, however it's vital to ensure the individual components within the network, their connections outside the network, and the data transmitting across the network are completely secure. If those components are engineered to be appropriately secure, then the security of the network as a whole is an extra layer ensuring a lower number of vulnerabilities, rather than being the last line of defense.&lt;/p&gt;
&lt;p&gt;On the flip side, you might observe that since all of the constituent components of the network are secure, it seems like the network itself doesn't need to be a focus of our vigilance. Au contraire! Not only do you lose the multiple layers of security which I described above, but there are some advantages you get from the network security layer.&lt;/p&gt;
&lt;p&gt;Firewalls, VPNs, and intrusion detection systems can be configured for the entire network, giving a blanket level of security for all of its systems - not that they're relying on this - important! These intrusion detection systems are particularly interesting and can't really operate as well within the context of an individual service.&lt;/p&gt;
&lt;p&gt;Denial-of-service attacks, particularly of the distributed sort, are famous and capable of taking down entire systems. Individual services should be guarded against denial of service attacks, but the network as a whole should be as well. Rate limiting, traffic filtering, and a good load balancer can be employed at the network level to alleviate this concern. I think cloud providers have products specifically for these sorts of attacks as well, but I'm not sure how effective they are.&lt;/p&gt;
&lt;p&gt;The topology of the network can help its security too. Segmenting the network into isolated areas can help contain breaches, which combined with firewalls and intrusion detection systems can give you a lot of control and insight into the state of the system. Ensuring that no actor in the network are trusted - even internal ones - helps to maintain the focus on security throughout the system, forcing proper auth checks at each stage.&lt;/p&gt;
&lt;h1&gt;React to Attacks and Vulnerabilities&lt;/h1&gt;
&lt;p&gt;As I mentioned before, the number of vectors for attack are too many to be able to guard against all of them. Unless you have a particularly honed security mind (I do not) then you're unlikely to be able to imagine a lot of them. I don't want to discount the importance of building secure systems, but in a lot of ways the most important action to ensuring a secure system is to actively monitor and respond to attacks and detected vulnerabilities. Adopting a security mindset isn't just about being cognizant of the technical aspects of security, but realizing our limitations and building practices and processes to shore up our shortcomings.&lt;/p&gt;
&lt;p&gt;Routinely scanning and auditing for vulnerabilities is the first step, and when we know that we don't know what we don't know (how many times can I say &amp;quot;know&amp;quot; in a sentence), this practice can be the best teacher for us. If your code is hosted in GitHub, it &lt;a href="https://github.com/security"&gt;has excellent security scanning products&lt;/a&gt; which can help to stay on top of known vulnerabilities, particularly with respect to your code's dependencies. If you're fortunate enough to have a security-minded person on your team or a security team at your firm, they can potentially help you set up a process of auditing attack vectors in your services and system.&lt;/p&gt;
&lt;p&gt;You do need to prioritize fixing these, however! It's no good finding vulnerabilities only to shrug and say &amp;quot;probably not gonna get found&amp;quot;. Just because a system hasn't been exploited yet doesn't make it secure, and part of our professional duty is to deliver software as secure as we're able to. Known vulnerabilities must be prioritized.&lt;/p&gt;
&lt;p&gt;Once we're in prod, we still need to observe our systems as they run. We can't prevent all security issues before we deploy, so being able to catch them when they happen is crucial. Security Information and Event Monitoring (SIEM) systems are purpose-built for this kind of monitoring - if you're securing a distributed system I'd make the assumption that you're able to devote resources for a product like this.&lt;/p&gt;
&lt;p&gt;Again here, when alerts are raised they need to be prioritized and responded to - it's no good knowing the vulnerability exists (an if detected here, probably exploited) only to not fix it. It would be good to have a conversation about what the plan should be when a security breach is detected - this likely involves a lot of parts of the organization outside just engineering. I've seen a lot of firms - much bigger than you might expect - that didn't have these clearly laid out. Being explicit about what should be done for security incidents makes it easy for anyone to be able to react to the incidents when they occur, which strengthens the overall response from the team or firm.&lt;/p&gt;
</description>
      <pubDate>Thu, 30 May 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-05-30T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">thing_i_made_cfweaver</guid>
      <link>https://ian.wold.guru/Posts/thing_i_made_cfweaver.html</link>
      <title>Thing I Made: CFWeaver</title>
      <description>&lt;p&gt;Last week, I &lt;a href="https://ian.wold.guru/Posts/book_club_1-2025.html"&gt;wrote about&lt;/a&gt; some thoughts I had related to test generation and control flow in business logic. This followed an effort at work that naturally turned out to be a good candidate for a model-based testing strategy. From this, I developed the tool &lt;a href="https://github.com/IanWold/CFWeaver"&gt;CFWeaver&lt;/a&gt; to generate test scenarios from control flow models, which I've worked on over the last couple of weeks to get into a state that might be useful for others in a similar situation.&lt;/p&gt;
&lt;p&gt;CFWeaver is a very simple CLI: you input a control flow model and it outputs test scenarios to exhaustively cover all the possible paths through the control flow. The input file is written in valid Markdown with a special syntax, and it's able to output either HTML or Markdown. You can read more about its function on &lt;a href="https://github.com/IanWold/CFWeaver"&gt;its GitHub repository&lt;/a&gt;, but I'll give a small example here from the readme. I might have the following control flow model defined:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;# Get an Item

* Authenticate: Success | Failure = 403 ? I am not authenticated to access items
* Validate: Success | Failure = 400 ? the request is invalid
* GetFromDatabase: Found ? the item exists | Not found = 404 ? the item does not exist | Error = 500 ? The items table errors on select
* Authorize: Success = 200 | Failure = 401 ? I am not authorized to access the specific item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CFWeaver can understand this model, compute the various combinations of control flow states, and generate the following (each row of the table being a test scenario):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/IanWold/ianwold.github.io/master/Static/images/cfweaver.png" alt="CFWeaver" /&gt;&lt;/p&gt;
&lt;p&gt;I've published CFWeaver at v0.9.0, with a small laundry list of tasks to get to &amp;quot;1.0&amp;quot; - I'll push some of those up to the repo as good first issues, if this project is interesting to you please do feel free to contribute!&lt;/p&gt;
&lt;p&gt;This is far from a jack-of-all-trades though; I think there are some factors that constrain testing this way generally, and the utility of this tool specifically, which I think make the ideal use case for it a bit unique. There's not much to say about a simple CLI tool that can't be understood from its README, so below I'll elaborate a fair amount on my thoughts on using a model-based testing strategy in various scenarios.&lt;/p&gt;
&lt;h2&gt;Model-Based Testing&lt;/h2&gt;
&lt;p&gt;The overarching concept here is model-based testing, which covers any number of approaches to test generation where some model of the system under test (SUT) is made, &lt;em&gt;from&lt;/em&gt; which tests are generated. The model could be any number of things we normally use to model our software: a model of the input parameters, state diagrams, Markov chains, or in my case a control flow diagram. Some tools generate full test suites you can repeatedly run, some will randomly compute tests at the point of test execution, and others will stop short of generating the full test in favor of some description of the tests.&lt;/p&gt;
&lt;p&gt;That latter behavior - only generating a &lt;em&gt;description&lt;/em&gt; of the tests - is what CFWeaver does. Generating a full test suite which could be run directly against the SUT would have a few negative consequences. First, CFWeaver would almost certainly require much more fine-grained input about the nature of the SUT to be able to generate full, meaningful tests. This, in turn, raises the complexity and therefore development effort on my end. Finally, it would probably also restrict the types of systems for which CFWeaver could generate tests.&lt;/p&gt;
&lt;p&gt;So, in order to maximize utility and ease development, CFWeaver only generates scenarios to test, with the conditions to cause the scenario and the expected result. This makes CFWeaver suitable to generate scenarios for any &lt;em&gt;kind&lt;/em&gt; of system at any level of testing (unit, integration, e2e, and so forth), though there are natural constraints on when you'd want to employ a strategy like this.&lt;/p&gt;
&lt;p&gt;On constraints, the most important consideration before employing &lt;em&gt;any&lt;/em&gt; model-based strategy is the utility of the model from which the tests are generated. Since the tests aren't generated from the SUT itself, there needs to exist some model independent of that system. Does that separate model have a utility &lt;em&gt;other&lt;/em&gt; than allowing tests to be generated from it? How difficult is it to develop that model? Does that model need to be maintained and evolved in tandem with the system in order to continue to be able to adequately test the system?&lt;/p&gt;
&lt;p&gt;These are all important questions to ask. If the model has no use outside of being used to generate tests and is difficult to create and/or maintain, I'd suggest you have a poor candidate for model-based testing. Any practice, testing practices included, will not work well if they don't work harmoniously with your whole software process. If you naturally have models as a product of your process &lt;em&gt;and&lt;/em&gt; the sorts of tests that can be generated from that model are useful for the sort of system you have, &lt;em&gt;then&lt;/em&gt; you might want to consider a model-based testing strategy.&lt;/p&gt;
&lt;p&gt;Take a web service, for example. I work on web services a lot these days. Often, services will generate an OpenAPI spec file as a result of their build processes. This model is easily generated, and it has a lot of utility in providing documentation to consumers, maybe generating clients, and so on. A tool like &lt;a href="https://schemathesis.io/"&gt;Schemathesis&lt;/a&gt; that randomly tests your service based on its OpenAPI spec might naturally fit into the workflow for a service with a good OpenAPI definition.&lt;/p&gt;
&lt;p&gt;I developed CFWeaver from a similar position. I wrote a new service for my company, and the business logic of this service was based on what essentially amounted to control flow diagrams which had been collaborated on by various stakeholders. These models define the system and are maintained by the stakeholders independently of the system; when the system changes it will be as a result of some collaboration over these models. Being able to generate all the test scenarios from this model was greatly valuable in that I was able to verify the expected business requirements for the various technical faults that were possible.&lt;/p&gt;
&lt;h2&gt;Black or White Box Testing?&lt;/h2&gt;
&lt;p&gt;Model-based testing supports both of these strategies, depending on how you come about your models. For review, black-box testing treats the SUT as a &amp;quot;black box&amp;quot; into which it cannot see; tests are based on external understandings of the system or the output from the system. White-box is the opposite; tests are based on information gleaned from the code of the system itself.&lt;/p&gt;
&lt;p&gt;If I go into a codebase and read the code (or perform some other sort of static analysis) in order to come up with a model of a system, then I'm using model-based testing in a white-box manner. The project I described above that caused me to make CFWeaver is a black-box project; the model exists independently of the system, and the system is tested in a code-agnostic manner to ensure it conforms to that specification.&lt;/p&gt;
&lt;p&gt;White-box approaches are good for testing technical aspects of the system and uncovering unexpected business requirements or lack thereof. For a web service, the return codes for various types of technical failures often fall under the purview of the business requirements for a system, and sometimes these scenarios can't be understood independently of the implementation of the system. Creating an independent model when analyzing a codebase for these sorts of technical states is usually beneficial, and a tool like CFWeaver can help after setting up the model if manually generating all the different scenarios is too complex or tedious.&lt;/p&gt;
&lt;p&gt;Such an approach is (probably; usually) a one-time spelunking, and the model is probably discarded after the analysis and test scenario generation. However, it is a natural product of the analysis and useful for engineers collaborating while understanding a system. That's particularly useful when analyzing legacy (or otherwise poorly-understood) code.&lt;/p&gt;
&lt;p&gt;I'm generally of the opinion though that the great majority of business logic can - and should - be tested from a black box approach. The software doesn't exist on its own, it exists because of an external requirement for its specific function. I largely don't care about implementation when it comes to ensuring the system meets those independent requirements, and on top of that it's beneficial to keep tests decoupled from implementation so that I can refactor the SUT without having to touch the tests; this makes refactoring easy and best ensures the success of the refactor (or any sort of change to the code, for that matter: feature add, bug fix, and so on).&lt;/p&gt;
&lt;p&gt;I do not know, but if you asked me to guess I would say that model-based testing probably is less naturally suited to black-box testing because it's more rare that formal models exist independently of the SUT. Indeed, the OpenAPI spec example is probably the most ubiquitous one, and I luckily landed in a project for which I was able to formalize the independent model. Rarity aside, if the stars &lt;em&gt;do&lt;/em&gt; happen to align in a way that a project does maintain models independently of the system, then a model-based approach can be a powerful black-box testing tool.&lt;/p&gt;
&lt;p&gt;Black-box strategies naturally exist in some level of ambiguity; there is a large bit of information about the SUT that is missing: the implementation. As mentioned, this is desirable to focus the testing on the actual business requirements, but difficulty arises in needing to comprehend the full scope of those requirements. Different permutations of input or function or environmental states all need to be considered, so any tool that can help enumerate these states is desirable.&lt;/p&gt;
&lt;p&gt;Different systems are naturally suited to different kinds of modeling, and while every system does have a &lt;em&gt;control flow&lt;/em&gt;, it isn't necessarily the case that every system is &lt;em&gt;best&lt;/em&gt; understood by a model of its control flow. Plenty of system are though, and in those cases CFWeaver will be a good fit.&lt;/p&gt;
</description>
      <pubDate>Wed, 05 Feb 2025 00:00:00 Z</pubDate>
      <a10:updated>2025-02-05T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">three_laws</guid>
      <link>https://ian.wold.guru/Posts/three_laws.html</link>
      <title>Three Laws</title>
      <description>&lt;p&gt;There is plenty of conventional &amp;quot;folk&amp;quot;-ish wisdom to be had, and a lot of it is widely known if sometimes unintuitive. This knowledge coalesces in the form of popular &amp;quot;laws,&amp;quot; particularly in areas like management and economics. Plenty has been written about the application of these laws to the software industry, to varying degrees of acceptability. The implications they have for us can be surprising, and studying them can give an intuition for the unintuitive: how to organize the best team, deliver the most successful project, or architect the best codebase.&lt;/p&gt;
&lt;p&gt;Here's three which I find particularly helpful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parkinson's First Law&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Work expands so as to fill the time available for its completion&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is, if I go through a pointing session and come out with a two week estimate for a task, it will take two weeks. If I estimate one week, it just might come out to one week. There are indeed efforts which take more or less time, but efforts afforded more time &lt;em&gt;will&lt;/em&gt; use that time.&lt;/p&gt;
&lt;p&gt;This is one of the inefficiencies caused by estimating, and it becomes especially difficult if a culture of &amp;quot;it's okay to point a little higher just in case&amp;quot; develops. This has the potential to cause a feedback loop, where tasks are estimated with a bit of wiggle room, that room is &lt;em&gt;used&lt;/em&gt; (per Parkinson's), a new baseline is thus set and estimates expand again to maintain that &amp;quot;little room.&amp;quot; If this sets in it will grind a team to a halt eventually - there won't be time for small tasks and important quality work like refactors and vulnerability patches will be punted in favor of feature development.&lt;/p&gt;
&lt;p&gt;Do not estimate! There are other reasons beyond Parkinson's to not estimate, but this is a big one. Forecast instead. In fact, you don't need to measure time taken on cards, you &lt;a href="https://www.youtube.com/watch?v=QVBlnCTu9Ms"&gt;just need to count cards&lt;/a&gt; to get forecasting that's more specific than estimation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Law of Triviality&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;People in an organization devote a disproportionate amount of time to trivial issues&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think of this like an 80/20 proposition: 80% of our resources will be spent on the trivial issues and 20% on the actual thing we need to focus on. One explanation for why this happens is that the trivial issues are easier for many people to understand, biasing people to discuss these. Another reason is that these issues are easier to &lt;em&gt;disagree&lt;/em&gt; about - if I'm wrong about a complicated technical issue that might call my competence into question, whereas being wrong about inconsequential issues has, obviously, no consequence.&lt;/p&gt;
&lt;p&gt;Following the 80/20 idea, only commit 20% (the bare necessity) to tackling the issue needed. Should you have 15 folks on the call to go over the issue, or 3? Should that call be 60 minutes or 15? Should your meeting have an open - or no - agenda (allowing any topics to be discussed as they arise), or should you have an agenda with the 1-3 items that need discussing?&lt;/p&gt;
&lt;p&gt;Do not expand! Liberally cull resources from the problem-solving space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jevon's Paradox&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As the efficiency of consuming a resource is increased, the net consumption of that resource increases&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a paradox as you'd expect the resource consumption to decrease, but instead the new efficiency induces an extra demand on that resource. This is a well-known phenomenon in urban planning: when lanes are added to a highway, it tends to cause the highway to have more jams than it did before.&lt;/p&gt;
&lt;p&gt;As software engineers, we tend towards making things more efficient. New processors have more cores, algorithms run faster than old algorithms, cloud platforms can offer more &amp;quot;computes&amp;quot; per dollar, etc. We can fall in a trap thinking this can afford us to write code at higher-than-necessary levels, less efficient business logic, or that we can &amp;quot;just&amp;quot; use more cloud products. In the extreme this has manifested in an outsized adoption of distributed architectures; particularly microservices.&lt;/p&gt;
&lt;p&gt;Do not complexity! &lt;a href="https://grugbrain.dev/"&gt;Complexity very, very bad&lt;/a&gt;! When we write software or make architectural diagrams or the like, we talk about taking several &amp;quot;passes&amp;quot;. There's the &amp;quot;get it to work&amp;quot; pass, the &amp;quot;make it pretty&amp;quot; pass, and so on. Always make sure you have a &amp;quot;make it simple&amp;quot; pass. Heck, do several! How many arrows and boxes can you take out of the architectural diagram? How much code can you get rid of? How many iterations over that list of widgets can you consolidate?&lt;/p&gt;
</description>
      <pubDate>Wed, 20 Nov 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-11-20T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">topology_doesnt_change</guid>
      <link>https://ian.wold.guru/Posts/topology_doesnt_change.html</link>
      <title>The Topologies They Are a-Changin'</title>
      <description>&lt;p&gt;Some of my earliest memories are from stories that my grandfather used to tell. He had an interesting career and knew a lot of story-able folks, so like a lot of grandfathers he had a compendium of fantastic stories. One of my favorites was a time he taught a course to his colleagues at his work. He was an engineer at Honneywell and was required to give this lecture in spite of his less-than-adequate abilities as a teacher, and it happened to be on an unfamiliar subject. After this lecture his colleagues were asked to fill out a short review form for him; the punchline of the story was that my grandfather's favorite review was &amp;quot;Leigh took a boring subject and ... left it there!&amp;quot; This was greatly amusing to him.&lt;/p&gt;
&lt;p&gt;The topic on which he was required to lecture was, of course being germane to this article, topology. &lt;em&gt;His&lt;/em&gt; was a lecture on the subject in mathematics, so I'm hoping that network topology is a more interesting subject for us here, but be warned that I will take some delight if you leave a comment about this being a boring subject! I do not think so - network topology consists of the actual layout of all of the components and connections between them within a network, which is an area with which I think we all have hands-on experience.&lt;/p&gt;
&lt;p&gt;The fifth fallacy of distributed computing is &amp;quot;topology never changes&amp;quot;, the implication being that it changes constantly. Twenty years ago when these fallacies were written that was a concern, and I will suggest that it's a significantly higher concern today. Not only do we tend to build systems with more distributed components and more connections than we did twenty years ago, but nowadays we have &lt;em&gt;managed&lt;/em&gt; systems, we throw load balancers everywhere, and I'm not sure any of us knows &lt;em&gt;everything&lt;/em&gt; that cloud providers do behind the scenes. Heck, each time a client connects or disconnects that's a change in the topology, and sometimes it's not a trivial one.&lt;/p&gt;
&lt;p&gt;Our topologies change constantly and mostly due to automated processes. Thankfully, this is an area where practice has mostly kept up with the times, and the industry actually seems to be relatively good at writing systems which are resistant, to some degree, to changes in topology. Cloud providers assign names to services by default, modern application frameworks make some best practices the default, and so on. This is an excellent start but we oughtn't allow ourselves to be lulled into a false sense of security here. Just as with the other fallacies, errors induced by topology can cause some wild behaviors in our systems.&lt;/p&gt;
&lt;h1&gt;Problems from Topology&lt;/h1&gt;
&lt;p&gt;Topology changes can be the cause of any of the problems covered in &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;the other fallacies&lt;/a&gt;. Changing the location or address of a service can prevent its dependents from being able to contact it, adding ride-along services can induce latency, closely locating services can surface bandwidth issues, and so on for each of the issues we have (and will) discuss. To some extent then, applying the lessons learned from our exploration of the other fallacies will guard us against the problems we can encounter from topology changes.&lt;/p&gt;
&lt;p&gt;Were the list of fallacies just a list of sources for errors from distributed computing, then including topology changes might well be redundant. Instead of being &lt;em&gt;that&lt;/em&gt; list though, the fallacies are a matter of mindset - a list of human assumptions we ought keep ourselves from making. By adopting the assumption that the topology &lt;em&gt;will&lt;/em&gt; change, and frequently, we can eliminate this category of network instabilities from affecting our systems.&lt;/p&gt;
&lt;h2&gt;Services&lt;/h2&gt;
&lt;p&gt;Obviously, a service failing and removing itself from the topology can have knockon effects if there are other services which depend on it; this creates a &lt;a href="https://ian.wold.guru/Posts/the_network_is_reliable.html"&gt;network instability&lt;/a&gt; and can be protected against with a number of patterns. Equally concerning though is when services add themselves into the topology.&lt;/p&gt;
&lt;p&gt;These situations can cause balancing errors, sometimes localized to the area of the service itself, sometimes with ripple effects across the whole network. The configurations of the load balancers take on a greater importance here, even small inconsistencies can cause a service to underperform. At scale this can introduce issues related to latency and bandwidth.&lt;/p&gt;
&lt;p&gt;Even just when the services &lt;em&gt;change&lt;/em&gt;, say when an update is rolled out, should not be discounted as a change to the topology - while the new version occupies the same node as the previous version and fulfills the same contract, but it's not the same as the topology before the update. This makes these update times sensitive; the team should monitor the system at the time of the update, and it would also be best to retain with your historical logs when updates are pushed out. The more subtle a problem, the further down the line it's likely to be caught.&lt;/p&gt;
&lt;h2&gt;Connections&lt;/h2&gt;
&lt;p&gt;Services can be connected with each other in various ways. If a service needs to dispatch a call to another, that's an obvious connection. If one service consumes events which another is responsible for producing, that's another more abstracted connection. These connections can chain into very complicated dependency chains, and that complexity is a source of problems itself. If the complexity grows to the point that it can't be understood, or if appropriate telemetry isn't set up to give you insight into the system, those introduce human problems. Human problems are the most difficult to solve.&lt;/p&gt;
&lt;p&gt;There's a physical aspect to the connections as well - services which are physically, geographically more separated will have more connection issues. Even in a perfect world without packet drops, those communications will be slower. &lt;a href="https://en.wikipedia.org/wiki/Software-defined_networking"&gt;Software-defined networking&lt;/a&gt; approaches can really help, depending on the scope of the system, and this introduces the same sort of configuration issue I mentioned with the load balancers. These systems can also become very much affected by service updates or other triggers that might cause the dependency graph to change - either by calling different systems or by routing more or less traffic over a particular connection.&lt;/p&gt;
&lt;p&gt;This is where it's important to have a proper understanding of the conceptual topology; you might not be able to predict all the ramifications of a change, but at least you can get some good insights into what you might need to test. This is not just useful for large changes; if your topology is well-understood, this sort of analysis can become part of the working process.&lt;/p&gt;
&lt;h1&gt;Decouple Services from Topology&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;The&lt;/em&gt; way to prevent changes in topology from inducing these errors in the system is to decouple the system from the topology. This is almost a question-begging argument: &amp;quot;Disconnecting the system from the topology disconnects the topology from the system!&amp;quot; However, the system can't be disconnected from the topology, not entirely, as the topology arises from the system itself. The statement rather is to say that the system should be written with abstractions over the pieces which interact with the topology such that a specific topology is not required for any component to work correctly.&lt;/p&gt;
&lt;p&gt;Maybe the most obvious example of decoupling services from the specifics of the topology is IP and DNS. I'm sure you would give me quite a quizical look if I were to suggest that send requests from one service to another using the target service's IP address instead of its domain name; the IP address of the service will almost certainly change, so we'd definitely break our connection if we did that! We use DNS to escape this problem. Well, others too, but we're focused on topology. Instead of having to rely on a specific about the topology (IP), we use an abstraction (DNS) that hides underlying changes from us.&lt;/p&gt;
&lt;p&gt;Just as we have an expectation that services' IP addresses &lt;em&gt;will&lt;/em&gt; change, we should expect that &lt;em&gt;all&lt;/em&gt; aspects of the topology can change. I'm almost always a fan of not introducing abstractions until a codebase or system reveals a pattern that is clearly asking for an abstraction, but this is not a case for that. Distributed computing increases the complexity of our systems tenfold (at least), and a significant contributing factor to that is the &lt;em&gt;necessity&lt;/em&gt; of a host of abstractions.&lt;/p&gt;
&lt;h2&gt;Abstract the Message from its Delivery&lt;/h2&gt;
&lt;p&gt;This seems simple conceptually but ends up being more interesting in practice. The idea is that messages dispatched across a network have a &lt;em&gt;message&lt;/em&gt; that's being sent and some &lt;em&gt;address&lt;/em&gt; it's being sent to; these should never be coupled with each other. That seems simple - we'll just throw the URL of the target service (ServiceA) in the configuration of the service making the request (ServiceB)!&lt;/p&gt;
&lt;p&gt;That's a start - suppose we include a &lt;code&gt;ServiceABaseUrl&lt;/code&gt; config in ServiceB, then when ServiceB needs to send a request it can send it thus:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;result = httpHandler.SendRequest(
    url: $&amp;quot;{serviceAConfig.BaseUrl}/some/endpoint&amp;quot;,
    request: packet
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh wait, ServiceB is coupled to the &lt;em&gt;endpoint&lt;/em&gt; of ServiceA now! In some very simple scenarios this could well be enough, but we would probably be wise to be more resilient. Sure, we could throw all of the individual endpoints into the configs as well, and that gives us more flexibility. But there's still a couple problems. Less importantly, we're going to have tons of config values. More importantly, even with the values of ServiceA's address(es) in configs in ServiceB, the latter is &lt;em&gt;still&lt;/em&gt; coupled to specific addresses about the former. We'll need to involve another party in order to properly decouple these.&lt;/p&gt;
&lt;p&gt;A configuration management system might be a good next step given our hypothetical ServiceB -&amp;gt; ServiceA topology. This would be a third party in our architecture, distinct from ServiceA or ServiceB, which would manage the configuration values across the system, or across parts of the system you care about. Instead of having ServiceB's configurations specified in a configuration file or some other scheme deployed with the service, they'd be in this distributed component. Not only would ServiceB dynamically access these values at runtime, but the configurations in the management system can be dynamically updated at runtime.&lt;/p&gt;
&lt;p&gt;Thus, when the location of ServiceA changes, the configuration in the management system would be updated to point to the new location at ServiceA. This can get quite fancy if you also introduce a &amp;quot;downtime&amp;quot; config, specifying whether ServiceA is available or not, as it might be down momentarily while changing locations. While this gives us the proper level of abstraction, it has some negative effects.&lt;/p&gt;
&lt;p&gt;By introducing this distributed component, ServiceB will have to dispatch a network request each time it needs a config value. Sure, we could do a cache about that, but now ServiceB has to go through the whole rigamaroll of handling that. Caches are difficult and we want to avoid them. This ties in with the other problem with using a configuration management system in this way - it adds a fair bit of complexity, needing multiple config values potentially in order to satisfy the abstraction requirements we have.&lt;/p&gt;
&lt;p&gt;Fortunately, this idea describes the widely-known pattern &lt;em&gt;service discovery&lt;/em&gt;, and there are dedicated systems which can support this. Employing one of these systems is essentially the same pattern as above but with the complex logic already built in. If your use case(s) are appropriately small, using your own configuration management with your own logic on top might be the right solution, otherwise I'd encourage you to look into dedicated service discovery systems.&lt;/p&gt;
&lt;p&gt;You might notice a flaw or two with this pattern though. Each dependent service will need to maintain its own logic to connect to and understand the service discovery system; more microservices in the network will result in more updates to that state being required; the dependent services still need to dispatch requests in order to understand addresses; and so on. In short, if I have a complex topology, then this pattern may be insufficient.&lt;/p&gt;
&lt;p&gt;The next step up would be a &lt;em&gt;service mesh&lt;/em&gt; to provide an independent component dedicated to managing the traffic across the network, rather than just managing address configurations. Service mesh systems like &lt;a href="https://www.envoyproxy.io/"&gt;Envou&lt;/a&gt; or &lt;a href="https://istio.io/"&gt;Istio&lt;/a&gt; will have you deploy a &lt;em&gt;sidecar&lt;/em&gt; alongside each service to handle inbound and outboud communication. These are centrally coordinated through a control plane to route requests correctly (as well as security, telemetry, and whatnot). This allows each service to minimize the amount of configuration required to dispatch its requests.&lt;/p&gt;
&lt;h2&gt;Async Communication&lt;/h2&gt;
&lt;p&gt;It might seem quite dramatic to deploy a sidecar application along with &lt;em&gt;every&lt;/em&gt; service in a system, and indeed it's more resource-intensive and complicated on the infrastructure side. In practice it isn't terribly burdensome from an engineering standpoint, and it's not terribly complicated for the infrastructure team once they get it up and going. However, I sympathize with the inclination to want to avoid this if possible. Less is more.&lt;/p&gt;
&lt;p&gt;I've touched on the concept of asyncronous communication in &lt;a href="https://ian.wold.guru/Series/fallacies_distributed_computing.html"&gt;my other posts on the fallacies&lt;/a&gt;, but to quickly recap, this pattern can essentially be thought of as &lt;em&gt;reacting to events&lt;/em&gt; instead of &lt;em&gt;sending requests to get a response&lt;/em&gt;. In our previous example, ServiceB needed some resource from ServiceA so it dispatched a request to get that resource. In an asynchronous context, ServiceA would be publishing events notifying the system about updates to that resource, which ServiceB could then consume in order to maintain its own state for this resource.&lt;/p&gt;
&lt;p&gt;From a data normalization mindset this can seem like an antipattern, but in a distributed context it's often quite beneficial, as it allows an ultimate abstraction between services (particularly within the context we're discussing related to topology) while minimizing processing and communication overhead. Not all communication can be made asynchronous, but adopting asyncronous communication largely obviates the need for the more complicated patterns discussed earlier to handle request-response communication.&lt;/p&gt;
&lt;h2&gt;Self-Healing Architecture&lt;/h2&gt;
&lt;p&gt;This is a key concept running a line through the considerations I've made so far. &lt;em&gt;Self-healing architectures&lt;/em&gt; are, as their name implies, architectures which are designed to automate fault recovery as much as possible. For example, &lt;em&gt;service discovery&lt;/em&gt; and &lt;em&gt;service meshes&lt;/em&gt; provide tolerance by adjusting the routing through a system in the event of a failure at one point. Just as our services can be coupled to the topology when they dispatch requests onto the network, they can also be coupled to the topology when they encounter errors from it. Thus, implementing a self-healing architecture is a necessity in ensuring a proper decoupling between the services and topology.&lt;/p&gt;
&lt;p&gt;Implementing proper deployment strategies with automated rollbacks are quite beneficial at the service level, and these are facilitated by the patterns discussed earlier. &lt;a href="https://en.wikipedia.org/wiki/Blue%E2%80%93green_deployment"&gt;Blue-green deployments&lt;/a&gt;, whereby a new version of a service is installed alongside the old version, allows requests to be rolled onto the new version and rolled off if necessary. This can be done intelligently in a &lt;a href="https://martinfowler.com/bliki/CanaryRelease.html"&gt;canary release&lt;/a&gt; strategy for more control. Using appropriate health check or other monitoring tools, these can be entirely automated.&lt;/p&gt;
&lt;p&gt;Looking at a bit of a higher level, some of our self-healing patterns will themselves alter the topology, and so need to be carefully considered in the whole context so as to not introduce the errors we've discussed. Auto-scaling systems and load balancers will provide isolation across services and communication. The &lt;a href="https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern"&gt;circuit breaker pattern&lt;/a&gt;, also discussed previously, is widely employed to isolate failures by breaking the connection to failed services.&lt;/p&gt;
&lt;p&gt;Each of these, combined with rollback strategies at the service level, provide an excellent resilience for the system. Their necessity also demonstrates the central point that the &lt;em&gt;topology is always changing&lt;/em&gt;, giving us a strange feedback loop where the topology has to change in order to react, in part, to the effects of a changing topology. This cascade is an interesting subject for study!&lt;/p&gt;
&lt;h1&gt;Communication and Monitoring&lt;/h1&gt;
&lt;p&gt;I've discussed communication, monitoring, and &lt;em&gt;people process&lt;/em&gt; at the end of each of my posts on the fallacies, and I'm curious whether I've said enough or whether this discussion should take up half of each fallacy. Indeed, our processes are as important as the software we develop. We're never going to be able to engineer distributed systems which are perfectly resilient to the constantly-changing environments they run in, so errors are going to happen. &lt;em&gt;People&lt;/em&gt; who implement a &lt;em&gt;process&lt;/em&gt; involving both proactive and reactive &lt;em&gt;communication&lt;/em&gt; with each other (very difficult, I know) and &lt;em&gt;monitoring&lt;/em&gt; the production systems are the glue that holds it all together.&lt;/p&gt;
&lt;p&gt;This is very important then. Proactively, when manual changes are made to the topology these should be communicated in a way that all stakeholders can efficiently be made to know about them and have an opportunity to react if necessary. Most changes in topology are automatically done nowadays, I think, so it occurs to me that manual changes are probably quite serious or impactful. All the more reason to communicate.&lt;/p&gt;
&lt;p&gt;But that the vast majority of topology changes are minor and done without human intervention mean that we can only react to unhandled errors that crop up as a result. Where possible, systems which do these sorts of alterations should log what they do and when, allowing us to use our normal log monitoring tools to be able to correlate errors we observe with these updates.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://principlesofchaos.org/"&gt;Chaos engineering&lt;/a&gt; is, for a lot of engineers I know, the most exciting related process - probably more so for the destructive aspect. I don't know that employing this strategy is right for most organizations though, and this testing strategy really benefits larger distributed systems. In order to effectively deploy it, not only do you need excellent communication and collaboration, but the folks running the tests need to be quite diligent and competent.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The saving grace is that topology errors will impact more complicated systems over time. Sure, that can probably be said about all the fallacies, but I think this is the one where that primarily shows through. Given my simple example topology of ServiceB -&amp;gt; ServiceA, the topology isn't going to change that much and simple strategies will resolve problems there. However, that network still requires that we set up proper security, and network failues and latency still impact the user experience.&lt;/p&gt;
&lt;p&gt;I tried to outline, particularly with request-response messages, a way to be able to ramp up onto more elaborate strategies to deal with topology abstraction. If you're developing a new system or distributing a legacy system, you might not need to worry about these issues as much up front, but you certainly need to plan for worrying about them. Architect your components in a way that they can be updated with these considerations in future, and ensure you have proper monitoring and processes in place to start with.&lt;/p&gt;
&lt;p&gt;I think that network topology - understanding it and coding around it - is one of the more interesting aspects in distributed systems. I think I share that opinion with several colleagues, but maybe not most. If you find this subject boring generally, leave a comment as to whether I was able to convey a sense of interest or whether I left the subject there, so to speak!&lt;/p&gt;
</description>
      <pubDate>Wed, 19 Jun 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-06-19T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">using_interfaces</guid>
      <link>https://ian.wold.guru/Posts/using_interfaces.html</link>
      <title>Using Interfaces</title>
      <description>&lt;p&gt;I've previously &lt;a href="https://ian.wold.guru/Posts/four_deeply_ingrained_csharp_cliches.html#interfaces"&gt;written about this problem in the C# world&lt;/a&gt; that interfaces are overused. Some codebases seem to have the idea that a DI container is incapable of handling a service class if it doesn't have an interface. Other codebases define inexplicable, empty &lt;code&gt;IModel&lt;/code&gt; interfaces which just serve as a handle for reflection or something. Other codebases yet have the even stranger idea that every single class should have its own interface, and then if polymorphism is required, there are interfaces on top of those!&lt;/p&gt;
&lt;p&gt;Last I checked, which was admittedly a while ago, this is a problem in the Java world as well. To limited extents the problem exists in other interface-supporting languages like Go or TypeScript, though there are many more opportunities than just the interfaces to footgun yourself with TypeScript types. I hope that we might be able to disabuse our codebases of these faulty notions about interfaces. I'm not going to propose some sort of SOLID but for interface use, I just finished &lt;a href="https://ian.wold.guru/Posts/book_club_5-2024.html"&gt;writing about the problems of that methodology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rather, I'm going to motivate some instances when interfaces should be used, the suggestion being that if you are not encountered with one of the following then you should not use an interface. Interfaces are abstractions, and abstractions should be avoided until the codebase reveals their necessity. I'm very skeptical of so-called principles which suggest abstractions should be implemented in all cases, or indeed to be implemented before they are revealed through a first iteration of the code.&lt;/p&gt;
&lt;h1&gt;Polymorphism&lt;/h1&gt;
&lt;p&gt;Obviously, if you need multiple implementations of the same service, you should use an interface. &lt;code&gt;PayPalPaymentService&lt;/code&gt; and &lt;code&gt;GooglePayPaymentService&lt;/code&gt; are probably required to share an &lt;code&gt;IPaymentService&lt;/code&gt; interface.&lt;/p&gt;
&lt;p&gt;Polymorphism is particularly useful when I need to migrate from one implementation of a service to another implementation, but I can't do it all at once. Suppose my software had always processed payments through PayPal, but our firm decided to not renew a contract and now we need to switch entirely to Google Pay. Bit of a contrived scenario, but you get the gist.&lt;/p&gt;
&lt;p&gt;If I had only ever had one implementation of &lt;code&gt;PaymentService&lt;/code&gt;, it would not need an interface, and indeed I'm suggesting that this would have been correct. Much of the time we put interfaces on services with single implementations thinking about this potential future case that we're going to need to alter implementation. Being clear, I've encountered this situtation of needing to swap implementation a lot. However, I advocate for not implementing the interface before I start doing this swap because it's easy to add the interface at that point in time. I don't know what the future brings, I don't know &lt;em&gt;which&lt;/em&gt; services are going to need implementations swapped out, so I don't want to pollute my codebase with interfaces until it becomes necessary.&lt;/p&gt;
&lt;p&gt;Any IDE, even Vim, is going to be able to support you on this. If I have the single concrete implementation &lt;code&gt;PaymentService&lt;/code&gt;, I can do a global rename of &lt;code&gt;PaymentService&lt;/code&gt; to &lt;code&gt;IPaymentService&lt;/code&gt;, create &lt;code&gt;IPaymentService&lt;/code&gt; as a separate interface which gets a compiler error, then rename the payment service class to &lt;code&gt;PayPalPaymentService&lt;/code&gt; implementing the new interface. All of my code now references the interface, and I'll only need to update the DI registration and pull public methods up into the interface. This takes 5 minutes, or no more than 30 if you have a particularly tortured codebase.&lt;/p&gt;
&lt;h1&gt;Layer Boundaries&lt;/h1&gt;
&lt;p&gt;Almost every legitimate use of an interface is going to be because there are multiple concrete implementations of that service, but there are legitimate cases when I need to hide the implementation from components across a system. I contend that this could not possibly be anything other than a very large system which requires lots of wrangling.&lt;/p&gt;
&lt;p&gt;The best example would be the &lt;em&gt;modular monolith&lt;/em&gt; architecture. In this architecture, the various modules (domains, areas, or the like) of the application are not allowed to reference each other. Instead, there is a shared library which provides the abstractions (interfaces) of each of the modules which they in turn reference. A central host or shell module will provide the entrypoint for the application and use DI or a similar pattern to marshal the correct implementations from each module to the others depending on their abstractions.&lt;/p&gt;
&lt;p&gt;This pattern facilitates the individual modules to be distributed from the core monolithic application as becomes necessary, and it's a first step in implementing the &lt;a href="https://en.wikipedia.org/wiki/Strangler_fig_pattern"&gt;strangler pattern&lt;/a&gt;. It's an important pattern in making sense of very large codebases, and although it introduces a number of problems itself which do require care to resolve, I don't think that its reliance on interfaces is one of them. It's certainly overkill to use this pattern on a small or a new codebase, which leads me to suggest that implementing interfaces for services in anticipation of a future implementation of the modular monolith is improper.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)"&gt;Hexagonal architecture&lt;/a&gt; (AKA &amp;quot;onion&amp;quot; or &amp;quot;ports and adapters&amp;quot; architecture) is another example of using interfaces to support layer boundaries, where there is a domain boundary which all other layers reference, which contains the abstractions necessary to perform domain operations. Again here we want to be careful that this architecture only be used for codebases of an appropriate size and type, but it's inoffensive to use interfaces to support this sort of architectural style.&lt;/p&gt;
&lt;h1&gt;All Other Scenarios&lt;/h1&gt;
&lt;p&gt;Don't use interfaces. &lt;a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it"&gt;YAGNI&lt;/a&gt;. Ideally, when we see an interface in our code - when we consume one - some flashing light should be going off somewhere in our brain: this is more dangerous than consuming an implementation! I'm not suggesting anything dramatic, but consuming interfaces should be handled more diligently and with an absolute expectation of a change in underlying behavior. The casual use of interfaces causes us to forget this in a sort of boy-cried-wolf sort of way: because interfaces are ubiquitous and their implementations &lt;em&gt;don't&lt;/em&gt; actually change that much in practice, we stop caring. An interface is an abstraction, and abstractions are to be treated with respect and fear.&lt;/p&gt;
&lt;p&gt;One objection I can anticipate is that I did not account for using interfaces to facilitate mocking in unit tests. You might be inclined towards permitting interfaces for this case, but I am not. Indeed, &lt;a href="https://ian.wold.guru/Posts/book_club-2-2024.html"&gt;I am decidedly anti-unit-test&lt;/a&gt; but I recognize a difference in opinion exists here. I prefer a combination of integration testing and property testing, along with relegating business logic to an area of the code that doesn't have dependencies - ports and adapters is good for this!&lt;/p&gt;
</description>
      <pubDate>Sat, 08 Jun 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-06-08T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">why_dont_we_work_in_single_week_increments</guid>
      <link>https://ian.wold.guru/Posts/why_dont_we_work_in_single_week_increments.html</link>
      <title>Okay but Seriously, Why Don't we Work in Single-Week Increments?</title>
      <description>&lt;p&gt;Even if you're not following &lt;a href="https://www.scrum.org/resources/scrum-guide"&gt;the Scrum Guide&lt;/a&gt;, a &amp;quot;sprint&amp;quot; has become the default unit of time for software engineering teams to encapsulate a single chunk of deployable work. This seems natural: we increment the product, test it, release it, then repeat. If you're not using Scrum you're probably still using sprints, and &lt;a href="https://ian.wold.guru/Posts/reclaim_your_agile.html"&gt;even if you rename it something dumb like &amp;quot;cattle drive&amp;quot;&lt;/a&gt; it's still a sprint. Sprints are defined by setting a goal for the team: in the next X days, we want to accomplish Y. Y is usually a list.&lt;/p&gt;
&lt;p&gt;Two weeks is the default length of sprint time in our industry I think (I have no source to cite), though because one month was (arbitrarily?) selected as a maximum by the Scrum Guide you can find plenty of teams blowing this up to 4 or 5 weeks. There's some products and/or teams for whom this long measure is a necessity, maybe I guess. If that's you then this article doesn't apply.&lt;/p&gt;
&lt;p&gt;For the last many years, across two companies and many products delivered, my update every Monday morning has been &amp;quot;I do not remember Friday, here's what I'm going to do today.&amp;quot; I've recently stopped saying the bit about not remembering Friday. I genuinely don't remember Friday, and that's not entirely due to my upper-Midwest-level of alcohol consumption; plenty of teetotalers can't either. Or refuse to. It's probably more that I refuse to remember anything past the weekend. I've been told &lt;a href="https://en.wikipedia.org/wiki/Severance_(TV_series)"&gt;Severance&lt;/a&gt; is a good commentary on this phenomenon.&lt;/p&gt;
&lt;p&gt;Weekends are an excellent natural break in the flow of things. 4 to 5 days (or 30 to 40 hours) is a nice chunk of time to accomplish a task. Why do I have to set a goal longer than a week? The longer I work in more-than-one-week sprints the less I understand the idea. There's plenty of sorts of work that require more than one week to figure; the week length is an individual human-level division of time. If there's work that needs to be scheduled on longer timeframes, I propose &lt;em&gt;also&lt;/em&gt; having broader monthly goals which can be chipped away at in weekly increments.&lt;/p&gt;
&lt;p&gt;A separate, but related, topic is that meeting every single morning about daily progress is probably functionally useless in the majority of cases, unless grinding swathes of sofware engineers down into nihilists is &amp;quot;functional&amp;quot;. Meet on Monday and Friday to set and reflect on goals, respectively. Meet extra twice a month for the same purpose for the whole month. 10-12 meetings/month.&lt;/p&gt;
&lt;p&gt;I've read of teams that already effectively do this in practice but they call the month unit the &amp;quot;sprint.&amp;quot; I have no beef with you all, carry on your excellent work. To the rest of us, I propose that we should unite as we have nothing to lose but our &amp;quot;I don't remember what I did on Friday&amp;quot;s.&lt;/p&gt;
</description>
      <pubDate>Sun, 11 Jan 2026 00:00:00 Z</pubDate>
      <a10:updated>2026-01-11T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">why_i_have_this_blog</guid>
      <link>https://ian.wold.guru/Posts/why_i_have_this_blog.html</link>
      <title>Why I Have This Blog</title>
      <description>&lt;p&gt;In 2013 I, a young software engineer, purchased &lt;code&gt;ianwold.com&lt;/code&gt; and set up a simple site to serve as a landing page for myself. I figured it would be good to have &lt;em&gt;some&lt;/em&gt; internet presence, considering I wasn't terribly attracted to Twitter or other social media as a way of advertising myself (as was the fashion at the time). I published a quick post about a &lt;a href="https://packagecontrol.io/packages/Monokai%20Gray"&gt;Sublime Text color scheme&lt;/a&gt;, and this initiated a nearly 10-year period &lt;a href="https://ian.wold.guru/Series/past_articles.html"&gt;with just six measly articles&lt;/a&gt; on my blog.&lt;/p&gt;
&lt;p&gt;The last article I published then in 2016 as &lt;a href="https://ian.wold.guru/Posts/sprache.html"&gt;an introduction to the Sprache framework&lt;/a&gt; isn't so bad re-reading it. It could certainly be better, no doubt.&lt;/p&gt;
&lt;p&gt;Over that decade I continued to work on my personal site - I have always made use of it as my landing page - though I neglected the blog. I spent a fair amount of time getting the site to look just as I want it to, I created &lt;a href="https://github.com/IanWold/Metalsharp"&gt;my own static site generator&lt;/a&gt; for it (&lt;em&gt;that&lt;/em&gt; project might be next in my sights to actually get across the finish line), and most importantly I upgraded my domain to the ultra-cool &lt;code&gt;ian.wold.guru&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I continued to not touch my blog though. Being fair, it wasn't really something which I needed for any particular reason, and I've never really aspired to being a writer. No need + no want = no work; fair enough.&lt;/p&gt;
&lt;p&gt;This changed a year ago when I decided to break my streak to write about &lt;a href="https://ian.wold.guru/Posts/deploying_aspdotnet_7_projects_with_railway.html"&gt;deploying ASP projects to Railway&lt;/a&gt;, the minimalist cloud provider. I was deploying a separate project on Railway and couldn't find the information I needed in one place, so I wrote that article to document the process for my future self and others. This isn't terribly different from the technical documentation I wrote for work, but crucially it is for a popular audience. Such writing should be more approachable, and it seems that at least some effort should be made to prevent it from being too dry.&lt;/p&gt;
&lt;p&gt;This coincided with a period where I was making some updates to my site (this is a good annual practice, BTW), and one of those updates I made was to include Giscus to allow for comments on blog posts, so &lt;a href="https://ian.wold.guru/Posts/giscus_is_awesome.html"&gt;I wrote another post on that&lt;/a&gt;. Days later, having returned from an excellent conference, I was inspired to share a set of links to some papers I like, which I thought would be pretty funny to call a &amp;quot;book club&amp;quot;. Okay, maybe just a bit amusing at best.&lt;/p&gt;
&lt;p&gt;That was my first three posts since 2016, all back-to-back, because I had a reason to publish them. Equally importantly, it did convey a sense of being &lt;em&gt;alive&lt;/em&gt; on my homepage - there's nothing like opening someone's site only to see no obvious signs of activity for six years. That was exciting to me, and at that moment I remember jokingly thinking that I was then set for the next eighteen years!&lt;/p&gt;
&lt;p&gt;As it happens, that was not meant to be. In the same way that all bad ideas come into being, I started thinking. It felt good to have those articles on my homepage, but did it feel good to &lt;em&gt;write&lt;/em&gt; them? In fact, it felt a bit clunky and forced. I feel the information on them is good, but there's no doubt the prose could be better. That was an interesting thought to me, as I do need to write for my work, and increasingly so - I need to be able to convey sometimes complex decisions and requirements though writing somewhat frequently. As I mentioned above, at work I do have the luxury of assuming a baseline knowledge with the domain &lt;em&gt;and&lt;/em&gt; the ability to be somewhat dry (seeing as it's a captive audience).&lt;/p&gt;
&lt;p&gt;Wouldn't it be better if I was able to write more inclusively and with more interesting prose professionally though? This is a tautology (&amp;quot;wouldn't it be better if it was better?&amp;quot;) so the obvious answer is yes. It occurred to me though, doubtless as it has for others, that writing publicly would force me to develop this skill. Thus, last year I began a writing streak that continues to this day - hoping to get four posts per month on average. And not just any posts, I figure the exercise should be purposeful for any poor souls who accidentally fall upon this blog.&lt;/p&gt;
&lt;p&gt;So I'm here to report, one year later, on the success of this project. Though I have had a bit of a lower output in recent months with a busier schedule in the summer, I've been able to keep to publishing posts, some of which are even useful! I like to think I've gotten a bit better at writing in this way, though I suppose it will probably take a good deal longer to properly develop. Importantly though, I find that I do enjoy it a fair bit.&lt;/p&gt;
&lt;p&gt;There's practical benefits to it beyond developing a personal skill. Chiefly, I think it's a smart professional move: it gives a wider SEO footprint, demonstrates your professional engagement, and it seems to carry a distinctive aesthetic quality which is overall beneficial for one's image. Now I might have gone and sullied that image with some clunky posts, but the other benefits are there!&lt;/p&gt;
&lt;p&gt;Looking back, I think it's safe to say that reengaging with my blog was among the best decisions I made last year. I'm very happy with the result, and I'm going to keep this blog going forward. It's the best way to continue to hone a writing skill, I think it does (or at least can) present me professionally quite well, and it allows me to give a small bit of konwledge back to the community. If you read this and want to start blogging, I would certainly encourage you to start. I started with an HTML page hosted by GitHub pages, and that's really all you need, though there are a plethora of fun platforms or tools or packages you can tinker with.&lt;/p&gt;
&lt;p&gt;If you like anything that I've written, I'd encourage you to reach out - you can comment on any of my posts, or cooler yet you can &lt;a href="https://ian.wold.guru/Posts/ive_indiewebbed_my_site.html"&gt;webmention me&lt;/a&gt;! I continue to have a general aversion to social media, and I'm excited by the prospect that a homepage can stand in as a platform for connecting with folks across the web.&lt;/p&gt;
</description>
      <pubDate>Mon, 09 Sep 2024 00:00:00 Z</pubDate>
      <a10:updated>2024-09-09T00:00:00Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">write_your_own_rdbms_versioned_migration_boilerplate</guid>
      <link>https://ian.wold.guru/Posts/write_your_own_rdbms_versioned_migration_boilerplate.html</link>
      <title>Write Your Own RDBMS Versioned Migration Boilerplate</title>
      <description>&lt;p&gt;If you're using a relational database, even perhaps for a small personal project, you've almost certainly had to have a serious think about migrations. Some databases require very heavily-engineered migration systems to be able to handle large, complex, and/or frequently-changing data. Other databases are very small and might do just fine with a single migrations file, or maybe even manual migrations computed off a shared schema script, if updates are few and far between.&lt;/p&gt;
&lt;p&gt;There's many different migration strategies, and there's no one-size-fits-all approach - different databases and applications can require vastly different strategies. One of the best and most ubiquitous strategies is the versioned migration, where individual updates are stored in SQL scripts that have an incremental version. I like a setup where, on startup, my application consults a migration history table in my database to see what the latest migration is, and to run any new migration scripts in sequential order.&lt;/p&gt;
&lt;p&gt;In my experience this setup works very well, and it can scale to a large size of project, database, or team. I start almost all of my projects - big or small, personal or professional - with this strategy, and I think you should consider making this strategy (or some variation of it) your default as well.&lt;/p&gt;
&lt;p&gt;One holdup though - isn't that a lot of overhead for a small, personal project? Should I really be investing the time to set up &lt;a href="https://flywaydb.org/"&gt;Flyway&lt;/a&gt; for every little API I want to set up? I contend no and no. I keep a snippet of boilerplate code to handle these migrations that I copy for every new project. It's a small amount of code, and I can modify it as needed per-project if it requires anything special. Best of all, my entire database from the start is migration-versioned, making it easy in future to switch to another system or onto Flyway if needed.&lt;/p&gt;
&lt;h1&gt;Versioned Migrations&lt;/h1&gt;
&lt;p&gt;As I described, this strategy involves breaking your migrations down into individual scripts for each discrete migration, and assigning them a version. What belongs in an individual file is up to you. You can restrict each file to only containing a single update on a table or column, or you could say that each feature card should have a single migration script. I prefer an in-between where each script contains a logically coupled, discrete set of changes, such that the database is valid at any migration version.&lt;/p&gt;
&lt;p&gt;However you split these up, you might have several migration scripts:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext"&gt;/ Migrations
  |- 01_CreateUsersTable.sql
  |- 02_CreateItemsTable.sql
  |- 03_CreateListsTable.sql
  |- 04_AddListIdToItemsTable.sql
  |- 05_AddUserIdToListsTable.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need some way to assign a version to each script. I prefer adding the version to each file name (as Flyway does, if I recall correctly) so that it's easy to see, and is aligned by version in my file system. If you prefer otherwise, you could maintain a separate map in your code or a config file from a file name to a version number, or no doubt any number of other strategies.&lt;/p&gt;
&lt;p&gt;The other aspect of this migrations strategy is that we will need to maintain a table containing the migration history of the database. I prefer a simple table myself:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE migration_history(
    &amp;quot;version&amp;quot; bigint primary key,
    &amp;quot;migrated&amp;quot; timestamp default NOW()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this way, I can keep track of where each database is at and apply migrations accordingly. When my app starts up, I'll look at this table to see what the latest version is. If the app sees that there are migration files exceeding this version, then I can run those migration scripts in the order they're intended.&lt;/p&gt;
&lt;h1&gt;Run the Migrations&lt;/h1&gt;
&lt;p&gt;Our migration-running code needs to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check the latest migration version, or create the &lt;code&gt;migration_history&lt;/code&gt; table if it's a new database,&lt;/li&gt;
&lt;li&gt;Find all the migration scripts after the latest version&lt;/li&gt;
&lt;li&gt;Execute these scripts in order, updating the &lt;code&gt;migration_history&lt;/code&gt; as it goes&lt;/li&gt;
&lt;li&gt;Commit the changes (important)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'll be demonstrating this with C# and PostgreSQL (via Npgsql), but this approach will work in any language with any RDBMS. The code should be straightforward enough for you to translate to whatever your case is.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class DatabaseMigrator
{
    static int GetLatestVersion(NpgsqlConnection connection, NpgsqlTransaction transaction) // TODO
    static IEnumerable&amp;lt;(int, string)&amp;gt; GetNewMigrationFiles(int latestVersion) // TODO
    static void RunMigrationFile((int version, string name) file, NpgsqlConnection connection, NpgsqlTransaction transaction) // TODO

    public static void Migrate(string connectionString)
    {
        using var connection = new NpgsqlConnection(connectionString);
        using var transaction = connection.BeginTransaction();

        var latestVersion = GetLatestVersion(connection, transaction);
        var newMigrationFiles = GetNewMigrationFiles(latestVersion);

        foreach (var file in newMigrationFiles)
        {
            RunMigrationFile(file, connection, transaction);
        }

        transaction.Commit();
        connection.Close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then call &lt;code&gt;DatabaseMigrator.Migrate(connectionString);&lt;/code&gt; from your startup logic, and it's all wired up! We can focus then on implementing each of the TODOs here.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GetLatestVersion&lt;/code&gt; is probably the most complicated of these, because we'll want to check whether &lt;code&gt;migration_history&lt;/code&gt; exists before we try to consult it, and create it if not. Before we get started implementing that method though, we'll want to write a little boilerplate to excute some queries on the database.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static NpgsqlCommand GetCommand(string query, NpgsqlConnection cnonnection, NpgsqlTransaction? transaction)
{
    var command = connection.CreateCommand();

    command.Connection = connection;
    command.CommandText = query;

    if (transaction is not null)
    {
        command.Transaction = transaction;
    }

    return command;
}

static void Command(NpgsqlConnection connection, NpgsqlTransaction transaction, string query)
{
    var command = GetCommand(query, connection, transaction);
    command.ExecuteNonQuery();
}

static T Query&amp;lt;T&amp;gt;(NpgsqlConnection connection, string query)
{
    var command = GetCommand(query, connection);
    return (T)command.ExecuteScalar();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you wanted to implement all the logic here to check that the result actually is a &lt;code&gt;T&lt;/code&gt; and handle that in a special flow, you can do. However, this is good enough for me - this code is running in a way where it's unlikely for me to encounter an exceptional scenario but in an &lt;em&gt;exceptional&lt;/em&gt; scenario, and if this code fails I want to let it throw anyway so that my app crashes and its health endpoint responds with a failure. Your scenario may well differ though.&lt;/p&gt;
&lt;p&gt;With that, I can outline &lt;code&gt;GetLatestVersion&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static int GetLatestVersion(NpgsqlConnection connection, NpgsqlTransaction transaction)
{
    var migrationHistoryExists = Query&amp;lt;bool&amp;gt;(
        connection,
        &amp;quot;SELECT EXISTS(SELECT 1 FROM pg_tables WHERE tablename = 'migration_history')&amp;quot;
    );

    if (migrationHistoryExists)
    {
        return Query&amp;lt;int&amp;gt;(
            connection,
            &amp;quot;SELECT MAX(version) FROM migration_history&amp;quot;
        );
    }
    else
    {
        Command(connection, transaction,
            &amp;quot;&amp;quot;&amp;quot;
            DROP SCHEMA public CASCADE;
            CREATE SCHEMA public;
            GRANT ALL ON SCHEMA public TO postgres;
            GRANT ALL ON SCHEMA public TO public;
            COMMENT ON SCHEMA public IS 'standard public schema';

            CREATE TABLE migration_history(
                &amp;quot;version&amp;quot; bigint primary key,
                &amp;quot;migrated&amp;quot; timestamp default NOW()
            )
            &amp;quot;&amp;quot;&amp;quot;
        );

        return -1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given that, it's quite easy to implement &lt;code&gt;GetMigrationFiles&lt;/code&gt;. The only peculiarity of that method is that it will return a tuple containing the version and file name for each file, so that it's easy for the other code to reference. Here I'm assuming all the migrations are in the &amp;quot;/Migrations&amp;quot; directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static IEnumerable&amp;lt;(int, string)&amp;gt; GetNewMigrationFiles(int latestVersion) =&amp;gt;
    new DirectoryInfo(&amp;quot;/Migrations&amp;quot;).GetFiles()
    .Where(f =&amp;gt; f.Extension == &amp;quot;.sql&amp;quot;)
    .Select(f =&amp;gt; (version: Convert.ToInt32(f.Name.Split('_')[0]), file: f.FullName))
    .Where(f =&amp;gt; f.version &amp;gt; latestVersion)
    .OrderBy(f =&amp;gt; f.version);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can probably be more concise using query syntax and defining the version with &lt;code&gt;let&lt;/code&gt; but I'll leave that as an exercise for the reader.&lt;/p&gt;
&lt;p&gt;The only thing left then is to run these scripts and update the migration history:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;static void RunMigrationFile((int version, string name) file, NpgsqlConnection connection, NpgsqlTransaction transaction)
{
    var query = &amp;quot;&amp;quot;;
    using (var reader = new StreamReader(file.name))
    {
        query = reader.ReadToEnd();
    }

    conn.Command(connection, transaction, query);
    conn.Command(connection, transaction, $&amp;quot;INSERT INTO migration_history (version) VALUES ({file.version})&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it! With just a 100-line C# file we've got fully-versioned migrations!&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;You can find all the code together on &lt;a href="https://gist.github.com/IanWold/d466f0e7e983da7b09e5ecc6bf719341"&gt;this GitHub Gist&lt;/a&gt;. I copy this for every project I start, and I start each database out with versioned migrations.&lt;/p&gt;
&lt;p&gt;You don't need to start your project off with a dependency on a third party migration library, you don't need to jump through any hoops - technical or conceptual - in order to get versioned migrations, and starting out with this puts you on the most solid path from the start. In future as your project evolves, if you end up in the rare situation of needing more features in your migrations, the code is right here for you to add it! If you end up needing so many migration features that a library like Flyway makes more sense, your story for switching to Flyway will be very easy. indeed.&lt;/p&gt;
&lt;p&gt;Happy migrating!&lt;/p&gt;
</description>
      <pubDate>Sat, 25 Nov 2023 00:00:00 Z</pubDate>
      <a10:updated>2023-11-25T00:00:00Z</a10:updated>
    </item>
  </channel>
</rss>