<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>realfiction by Frank Quednau</title><description>RSS feed of the most recent 10 items</description><link>https://realfiction.net/</link><language>en-en</language><item><title>Silencing macOS emoji suggestions</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Thu, 07 May 2026 22:45:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple OSX at some point got this idiotic suggested emojis feature which falls
exactly into this category of UX labelled &amp;quot;meant to be helpful, doing the opposite&amp;quot;.&lt;/p&gt;
&lt;p&gt;There is a way to make it stop. Run this in a terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;defaults write /Library/Preferences/FeatureFlags/Domain/UIKit.plist emoji_enhancements -dict-add Enabled -bool NO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A reboot later and the suggestions are gone.&lt;/p&gt;
&lt;p&gt;Note that writing to &lt;code&gt;/Library/Preferences&lt;/code&gt; requires &lt;code&gt;sudo&lt;/code&gt; — if the command above fails with a permission error,
prefix it with &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Info&amp;gt;
macOS updates can reset feature flags, so if the suggestions ever come back after an OS update,
just run the command again.
&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;p&gt;Now, how to fix the other &amp;lt;u&amp;gt;347&amp;lt;/u&amp;gt; issues that Tahoe has…&lt;/p&gt;
</content:encoded></item><item><title>Shareholder value</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Sun, 15 Feb 2026 22:15:00 GMT</pubDate><content:encoded>&lt;p&gt;This cartoon by Tom Toro is an absolute classic, and it really keeps growing on you. Indeed, for a teeny,
tiny moment we had &lt;em&gt;so much shareholder value&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/shareholder-value.png&quot; alt=&quot;Three children sit around a small campfire in front of a ruined city skyline as a man in a tattered business suit tells them, &apos;Yes, the planet got destroyed. But for a beautiful moment in time we created a lot of value for shareholders.&apos;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The cartoon &lt;a href=&quot;https://www.newyorker.com/cartoon/a16995&quot;&gt;appeared in the New Yorker&lt;/a&gt;, and &lt;a href=&quot;https://www.insidehook.com/culture/story-tom-toro-new-yorker-climate-cartoon&quot;&gt;Tom Toro said himself in an interview&lt;/a&gt; that the image is from
November 25th 2012.&lt;/p&gt;
</content:encoded></item><item><title>What is (anti-)conservatism?</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Tue, 27 Jan 2026 21:30:00 GMT</pubDate><content:encoded>&lt;p&gt;I have seen this quote go round here &amp;amp; there, and I simply want to recreate what appears to be a &lt;a href=&quot;https://crookedtimber.org/2018/03/21/liberals-against-progressives/#comment-729288&quot;&gt;comment from Frank Wilhoit&lt;/a&gt; on a post
from 2018, just in case it goes away for whatever reason (&lt;em&gt;the blog I&apos;m linking to is similarly old as mine, but we should cover our backs&lt;/em&gt;):&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There is no such thing as liberalism — or progressivism, etc.&lt;/p&gt;
&lt;p&gt;There is only conservatism. No other political philosophy actually exists;
by the political analogue of Gresham’s Law, conservatism has driven every other idea out of circulation.&lt;/p&gt;
&lt;p&gt;There might be, and should be, anti-conservatism; but it does not yet exist. What would it be? In order to answer that question, it is necessary and sufficient to characterize conservatism. Fortunately, this can be done very concisely.&lt;/p&gt;
&lt;p&gt;Conservatism consists of exactly one proposition, to wit:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There must be in-groups whom the law protectes but does not bind, alongside out-groups whom the law binds but does not protect.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is nothing more or else to it, and there never has been, in any place or time.&lt;/p&gt;
&lt;p&gt;For millenia, conservatism had no name, because no other model of polity had ever been proposed. “The king can do no wrong.” In practice, this immunity was always extended to the king’s friends, however fungible a group they might have been. Today, we still have the king’s friends even where there is no king (dictator, etc.). Another way to look at this is that the king is a faction, rather than an individual.&lt;/p&gt;
&lt;p&gt;As the core proposition of conservatism is indefensible if stated baldly, it has always been surrounded by an elaborate backwash of pseudophilosophy, amounting over time to millions of pages. All such is axiomatically dishonest and undeserving of serious scrutiny. Today, the accelerating de-education of humanity has reached a point where the market for pseudophilosophy is vanishing; it is, as The Kids Say These Days, tl;dr . All that is left is the core proposition itself — backed up, no longer by misdirection and sophistry, but by violence.&lt;/p&gt;
&lt;p&gt;So this tells us what anti-conservatism must be: the proposition that the law cannot protect anyone unless it binds everyone, and cannot bind anyone unless it protects everyone.&lt;/p&gt;
&lt;p&gt;Then the appearance arises that the task is to map “liberalism”, or “progressivism”, or “socialism”, or whateverthefuckkindofstupidnoise-ism, onto the core proposition of anti-conservatism.&lt;/p&gt;
&lt;p&gt;No, it a’n’t. The task is to throw all those things on the exact same burn pile as the collected works of all the apologists for conservatism, and start fresh. The core proposition of anti-conservatism requires no supplementation and no exegesis. It is as sufficient as it is necessary. What you see is what you get:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The law cannot protect anyone unless it binds everyone; and it cannot bind anyone unless it protects everyone.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>Experiences working with Claude Code</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Sun, 21 Sep 2025 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;Info icon={false} header=&amp;quot;Clarifying my viewpoint on LLMs&amp;quot;&amp;gt;
I sort of sit in a bubble on Mastodon where most people are heavily
opposed to the current incarnation of AI: LLM-based technology.&lt;/p&gt;
&lt;p&gt;I absolutely get it, while many would disagree, I share many values with those people.&lt;/p&gt;
&lt;p&gt;There is a lot to criticize and I don&apos;t want to reiterate all the points that have already been made:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The way cheap labor (aka modern-day slavery) has been used to categorize the training data&lt;/li&gt;
&lt;li&gt;The incredible amounts of electricity and other resources necessary to power the resulting models&lt;/li&gt;
&lt;li&gt;The fact that it destroys the democratization of software development&lt;/li&gt;
&lt;li&gt;The way how the most relevant players so easily bend the knee to the new US autocracy, which casts a dark shadow on how the technology is being used by the main players.&lt;/li&gt;
&lt;li&gt;The way it is being used to put pressure on employees.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is not wrong.&lt;/p&gt;
&lt;p&gt;But I&apos;ve lived half a century now. I don&apos;t hold humanity to a particularly high standard these days, myself included.
As far as apes go, we&apos;ve had an &lt;em&gt;incredible&lt;/em&gt; run.
I am actually positive towards the idea that we could develop some sort of new life-form, one that runs
on thinking stones. Who am I to accuse and blame the modest, brutal and chaotic origins of something that at some point may become a new form of consciousness?&lt;/p&gt;
&lt;p&gt;Oh, I&apos;m with you, LLMs are not the singularity. We haven&apos;t yet built models that can keep training themselves,
and they cannot experience reality through senses and interaction.
These are absolute show-stoppers when it comes to exploring the solution space through Evolution.&lt;/p&gt;
&lt;p&gt;But I certainly think it correct that we should keep trying. We humans are not the pinnacle of evolution.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(In fact, nothing ever is—as the solution space gets explored, who has the right to say that one exploration is better than the other?)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also don&apos;t share the very generic sentiment that this technology is useless.
Just yesterday, we took a photo my son&apos;s assignment, highlighted relevant parts of the text and verbally asked ChatGPT to help us understand
and give us an example by solving the first task. The whole interaction worked flawlessly.&lt;/p&gt;
&lt;p&gt;This is &lt;em&gt;absolutely bonkers&lt;/em&gt;. So the tech makes mistakes? Big deal!
It has always been and will be for the foreseeable future dangerous to take decisions made by software as some kind of ultimate truth.
Is your software free of mistakes?
In fact, is the way I live my life free of mistakes?&lt;/p&gt;
&lt;p&gt;Since when do we take marketing claims of VC dudes like Sam Altman at face value and
let them dictate what to expect of this new technology? This stuff is far from perfect and
yet it enables interactions previously deemed impossible with satisfactory accuracy.
It is necessary to start understanding its constraints and put in the work to navigate them.
&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;p&gt;For about 2 months I have been using Claude Code here and there at work and in private.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The background? About 25 years of work experience as consultant and later in product development.
Most of my career I have been working with C#, HTML, CSS, JavaScript and TypeScript.
Our product is now almost 8 years old, and for many things that happen in the system there is prior art.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;First assignment&lt;/h2&gt;
&lt;p&gt;The first assignment I wanted to do with Claude to see how it fares
was an Excel Importer for our product&apos;s new capability of importing users who are authenticated through our own IdP
and not Entra. The task here was to add a new command to our admin console and use things that we already have in place
these days and combine them such that we can create users from an Excel file that follows a template and is provided by a customer.&lt;/p&gt;
&lt;p&gt;Whatever Claude generates gets a full code review as well as testing before committing the output.&lt;/p&gt;
&lt;p&gt;This first usage went fairly well. One thing that I am happy about is that the tool can take certain chores away from me
that &lt;strong&gt;I like thinking about, but at 50 I don&apos;t particularly look forward to coding them out&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I can ask the tool to perform refactorings like &amp;quot;extract the part where we provide a row object for each line into its own class named ExcelFileReader&amp;quot;.&lt;/p&gt;
&lt;p&gt;The truth is, the initial code is almost always not to the standard of quality I expect in our codebase.
It usually does what you wanted it to do, but it will be up to me to bring it into the right &lt;em&gt;shape&lt;/em&gt;.
This would be annoying if you had to do it all by yourself, but with a combination of JetBrains Rider/ReSharper and
telling Claude what refactoring to perform, you get to where you want. Also, the refactoring capabilities in the TypeScript part
of the codebase are not as strong and Claude can perform such tasks.&lt;/p&gt;
&lt;h2&gt;Side note on speed of working&lt;/h2&gt;
&lt;p&gt;Am I faster now, having this tool? I don&apos;t know, probably not. But that to me is somewhat beside the point. Claude can be quite slow
and for very small tasks I may end up just doing it myself, as the shuffling of tokens feels wholly inefficient in that situation.&lt;/p&gt;
&lt;p&gt;However, the tool may lead me to tackle things that I deem useful but am too lazy to start.&lt;/p&gt;
&lt;h2&gt;What &lt;em&gt;is&lt;/em&gt; the point then?&lt;/h2&gt;
&lt;p&gt;In my position, I actually end up reviewing far more code than I write myself. I am involved in a number of activities around the software lifecycle,
and actually coding is often only a small part of it. So, when I tell Claude what to do, the end result pretty much exercises the same muscles that I
am already using, namely code review and giving feedback on what to improve.&lt;/p&gt;
&lt;h2&gt;Intermezzo: Many customers want access to LLM tools&lt;/h2&gt;
&lt;p&gt;Truth is: Customers ask for AI tools. Our product is what was in another era called an
Intranet. Many customers have tried ChatGPT privately or in their work and are often
satisfied with the results.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;How can this be?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Those people often work with facts well-known to them (intra-company facts).
The fact-checking is trivially done. The LLM can for example help people who aren&apos;t used to writing craft a decent article in the tone they&apos;re aiming for.&lt;/p&gt;
&lt;h2&gt;A proof of concept to work on how we can leverage LLMs in our product&lt;/h2&gt;
&lt;p&gt;On this POC I use Claude regularly, especially &lt;strong&gt;for the stuff I don&apos;t care about&lt;/strong&gt;,
but is necessary for a nice POC, while focusing on what I do care about.&lt;/p&gt;
&lt;p&gt;One comical error I did right when starting the POC was to ask too much in one go, like&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Set up an Aspire project containing a minimal API ASP.NET project,
a frontend SPA React project with Vite and a YARP-based BFF layer on top of
the two projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This completely overwhelmed Claude and at some point it even forgot that the whole solution was meant to be set up in Aspire,
deleted all Aspire dependencies and wanted to start orchestrating the processes with custom-written code.
Hilarious!&lt;/p&gt;
&lt;p&gt;Now I know better. The tasks need to stay crisp and clear and focus on specific outcomes.
Makes absolute sense, right?
I find Claude pretty bad at &amp;quot;architecting&amp;quot;. And you know what? That&apos;s fine, that&apos;s my job after all.&lt;/p&gt;
&lt;p&gt;Also, some hard coding stuff, e.g. a nested async iterator in the frontend to stream tokens into one message,
while starting a new message with a function call request, I need to do myself.
Also fine, I mean, I&apos;m paid for the hard stuff, right?&lt;/p&gt;
&lt;h2&gt;Writing and Using MCP tools&lt;/h2&gt;
&lt;p&gt;In between I also wrote two MCP tools that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start a work item in Azure DevOps and use the work item&apos;s content as task input.&lt;/li&gt;
&lt;li&gt;Start a PR on a given branch and work item describing what changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the sort of thing where an LLM can lower the barrier to entry. Looking up the necessary queries and APIs and dependencies to talk to Azure DevOps?
Not in this lifetime, I don&apos;t think so. Meanwhile, Claude was able to set this part up for me. I fully expect a connoisseur of the
Azure DevOps API to look at the code and say things like &lt;em&gt;&amp;quot;This can be simplified, there is a better API for that, etc.&amp;quot;&lt;/em&gt;,
but I understand the code, and the general quality is sufficient for its intended use
and maintainability.&lt;/p&gt;
&lt;p&gt;I would like to mention the MCP tool &lt;a href=&quot;https://context7.com&quot;&gt;Context7&lt;/a&gt;, which has helped me perform certain code changes
based on current documentation instead of whatever was used to train the current model in use.&lt;/p&gt;
&lt;h2&gt;What else have I used Claude for?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Code extraction refactorings&lt;/li&gt;
&lt;li&gt;Tasks with prior art I can point to. A well-factored codebase is beneficial to good outcomes.
&amp;quot;More of the same&amp;quot; works pretty well.&lt;/li&gt;
&lt;li&gt;Writing certain tests of high complexity. Yes, some of our tests are ugly and complex, e.g. around sign-in.
Yes, sometimes I code first and perform the testing steps afterwards, it happens.
Claude can help write me tests, especially when I say what to test for.&lt;/li&gt;
&lt;li&gt;Finding something back where I know there should be prior art. Claude can help me find it. &amp;quot;code that fits in your head&amp;quot;
is a good mantra, but the totality of ahead&apos;s code does not fit in anyone&apos;s head at once, so sometimes the question is where we did a certain thing before.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What is crap?&lt;/h2&gt;
&lt;p&gt;I already mentioned some limitations. What is quite annoying is that in order to stay sharp,
it makes sense to reset the context every so often. Then Claude has to spend some time again and again to &amp;quot;relearn&amp;quot; what it needs to know.&lt;/p&gt;
&lt;p&gt;What is also often quite sad is that the technology is simply unable to learn from its mistakes.
When it systematically fails at a specific problem, you can only hope that it surfaces to the
developers at Anthropic at some point. You yourself can&apos;t do much about it.
One could try to place a &amp;quot;reminder&amp;quot; in the &lt;code&gt;claude.md&lt;/code&gt; file, but, well, that is most certainly a big limitation of this technology.&lt;/p&gt;
&lt;h2&gt;In Conclusion&lt;/h2&gt;
&lt;p&gt;There is an unfortunate tension that while LLM tools are useful, the usefulness has been dwarfed
by investments undertaken by big tech. The limitations are clear by now, and once more, LLMs are no silver bullet. Why
some in big tech keep pretending otherwise is probably more dictated by the high financial stakes involved rather than actual outcomes
of using the technology.&lt;/p&gt;
&lt;p&gt;From my perspective, the monthly subscription cost seems appropriate. I can use the tech when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I have clear directions to give&lt;/li&gt;
&lt;li&gt;There is prior art&lt;/li&gt;
&lt;li&gt;The code needs to do something concrete rather than focus mainly on maintainability or other non-functional metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&apos;t get good results when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The directives are too vague&lt;/li&gt;
&lt;li&gt;The thing that the code needs to do is non-trivial.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Carl Sagan&apos;s Cosmos in the archive</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Thu, 26 Jun 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I saw today in a post that &lt;strong&gt;Carl Sagan&apos;s Cosmos&lt;/strong&gt; &lt;a href=&quot;https://archive.org/details/CosmosAPersonalVoyage&quot;&gt;has been made available on archive.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This show is about 45 years old and left a huge impression on me as a child. The narrator is &lt;a href=&quot;https://en.wikipedia.org/wiki/Carl_Sagan&quot;&gt;Carl Sagan&lt;/a&gt;,
to me one of our great mentors, an astute observer, a curious person, kind, thoughtful,
someone I deeply admire.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/carl-sagan.jpg&quot; alt=&quot;Carl Sagan explaining the Universe&quot;&gt;&lt;/p&gt;
&lt;p&gt;The soundtrack contains classical music as well as beautiful songs from Vangelis. The title track of &lt;strong&gt;Cosmos&lt;/strong&gt;
is just beautiful and sets the tone of this show.&lt;/p&gt;
&lt;p&gt;&amp;lt;YouTubeEmbed link=&amp;quot;https://www.youtube.com/embed/pYqI56IwNeg&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://archive.org/details/CosmosAPersonalVoyage&quot;&gt;Here&apos;s the link.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>The AI files - posts that are leaving an impression</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Sat, 07 Jun 2025 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am not ready yet to form coherent thoughts on AI, or rather, the subject has an almost unfortunate depth
as companies with a near-infinite war chest are pouring billions upon billions of cash into LLMs to make
the subject completely unavoidable in tech, drowning any critical voices by the sheer mass that has been
put into motion to only God knows which direction.&lt;/p&gt;
&lt;p&gt;Of course, many people have been thinking about the subject and this post collect a number of articles
from people who still try to retain their own voice within a vast growing sea of AI slop. This post is mostly
a collection of links to articles that have me thinking about the current trends in IT tech.&lt;/p&gt;
&lt;h3&gt;People Are Losing Loved Ones to AI-Fueled Spiritual Fantasies&lt;/h3&gt;
&lt;p&gt;At Rolling Stone &lt;a href=&quot;https://www.rollingstone.com/culture/culture-features/ai-spiritual-delusions-destroying-human-relationships-1235330175/?ref=404media.co&quot;&gt;they wrote about people&lt;/a&gt; who seem to lose themselves in some alternate reality where
LLMs sometimes seem all-too-capable to indulge you in whatever psychosis is becoming part of you.&lt;/p&gt;
&lt;h3&gt;Vibe coding may well destroy your ability to find solutions to problems out of your own intellect&lt;/h3&gt;
&lt;p&gt;This mastodon post describes a situation where a colleague is showing some strange behavior.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;He basically rotted his own brain by compulsively using ChatGPT in lieu of actually thinking with most
any of the projects he was working on. Instead of taking the time to read through code in our framework,
look up documentation, or do any sort of debugging, he instead just begged and pleaded with ChatGPT to try and get somewhere because &amp;quot;it was faster.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What are addictive activities? Indeed, repeated actions with variable reward.
Prompting a LLM chat window to solve a certain task &lt;a href=&quot;https://pivot-to-ai.com/2025/06/05/generative-ai-runs-on-gambling-addiction-just-one-more-prompt-bro/&quot;&gt;does carry quite a bit of that&lt;/a&gt;, if you think about it.&lt;/p&gt;
&lt;p&gt;But, &lt;a href=&quot;https://infosec.exchange/@dvandal/114638324336575392&quot;&gt;read for yourself&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;I Think I&apos;m Done Thinking About genAI For Now&lt;/h3&gt;
&lt;p&gt;This is &lt;a href=&quot;https://blog.glyph.im/2025/06/i-think-im-done-thinking-about-genai-for-now.html&quot;&gt;an account&lt;/a&gt; where the emotional reaction to the current crop of generative AI is strongly negative,
alas the author attempts to understand his position and raises awareness to important issues.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have been trying very hard to correct for this bias,
to try to pay attention to the facts and to have a clear-eyed view of these systems’ capabilities.
But the feelings are visceral, and the effort to compensate is tiring. It is, in fact,
the desire to stop making this particular kind of effort that has me writing up this piece and
trying to take an intentional break from the subject, despite its intense relevance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The rise of Whatever&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://eev.ee/blog/2025/07/03/the-rise-of-whatever/&quot;&gt;The rise of Whatever&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This was originally titled “I miss when computers were fun”. But in the course of writing it,
I discovered that there is a reason computers became less fun, a dark thread woven through a number of
events in recent history.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Where&apos;s the Shovelware? Why AI Coding Claims Don&apos;t Add Up&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;So if you&apos;re a developer feeling pressured to adopt these tools — by your manager, your peers, or the general industry hysteria — trust your gut.
If these tools feel clunky, if they&apos;re slowing you down, if you&apos;re confused how other people can be so productive, you&apos;re not broken.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://mikelovesrobots.substack.com/p/wheres-the-shovelware-why-ai-coding&quot;&gt;This article&lt;/a&gt; is again basically one data-point, but this person made the effort of estimating tasks and then
throwing a coin whether he&apos;d do it with AI or not, and then compare the outcome with his estimate. Absolutely interesting stuff.&lt;/p&gt;
&lt;p&gt;So where&apos;s the &lt;a href=&quot;https://en.wikipedia.org/wiki/Shovelware&quot;&gt;&lt;em&gt;shovelware&lt;/em&gt;&lt;/a&gt;?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I am certain I will add further updates to this post as interesting posts come along.&lt;/p&gt;
</content:encoded></item><item><title>☁️ Agnostic – On gremlins and graphs</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Sun, 04 May 2025 21:30:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;TopicToc
title=&amp;quot;Cloud agnostic series&amp;quot;
topicId=&amp;quot;azure-ahead&amp;quot;
active={frontmatter.title}
closed
/&amp;gt;&lt;/p&gt;
&lt;p&gt;Time to look at what may be by far the most exotic technology choice in ahead: The use of a graph database
in the form of the gremlin endpoint provided by Cosmos.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/cloud-agnostic-7.png&quot; alt=&quot;Graph view of Aspire-registered resources&quot;&gt;
&amp;lt;figcaption&amp;gt;Speaking of graphs - Aspire allows you to see your Resources and their interdependencies as a graph&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;This decision was taken about 7 years ago – a good part of it was related to my excellent
experiences with graph databases. Microsoft was already offering said endpoint for their Cosmos DB offering,
and after some proof of concept we went in.&lt;/p&gt;
&lt;p&gt;From today&apos;s perspective, I&apos;d rather choose &lt;em&gt;boring tech™&lt;/em&gt; over &lt;em&gt;superior tech™&lt;/em&gt;. Case in point,
it took me quite some time to settle on what a dockerized version of ahead could use from the &lt;a href=&quot;https://tinkerpop.apache.org/providers.html&quot;&gt;list of technologies
showcased&lt;/a&gt; on the tinkerpop (tinkerpop the tech vs gremlin the language, I assume, although I&apos;ve never been
100% clear on where to draw the line between the two terms) page.&lt;/p&gt;
&lt;p&gt;Generally, the tech feels fairly far away from .NET, even though &lt;a href=&quot;https://tinkerpop.apache.org/docs/current/reference/#gremlin-DotNet&quot;&gt;Gremlin.NET&lt;/a&gt; is a capable client to access
gremlin-enabled databases.&lt;/p&gt;
&lt;p&gt;After some back and forth I settled for &lt;a href=&quot;https://arcadedb.com/&quot;&gt;Arcade DB&lt;/a&gt;. It is a multi-model database (much like Cosmos is)
and offers Gremlin-related capabilities, &lt;a href=&quot;https://docs.arcadedb.com/#gremlin-api&quot;&gt;documented here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The infrastructure&lt;/h2&gt;
&lt;p&gt;The DB is also available as container – the following code shows how the resource is registered with Aspire:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed showHint repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_2&amp;quot; file=&amp;quot;AppHost/InfrastructureDependencies.cs&amp;quot; start={27} end={65} /&amp;gt;&lt;/p&gt;
&lt;p&gt;There&apos;s a bit to unpack here:&lt;/p&gt;
&lt;p&gt;In Aspire, we can define a connection string also as a resource. This method will then return the Graph DB resource
as well as the connection string necessary to connect to it as a resource as well. This is done
in the &lt;code&gt;AppHost&lt;/code&gt; project as follows:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_2&amp;quot; file=&amp;quot;AppHost/Program.cs&amp;quot; start={30} end={37} /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Info&amp;gt;
In the code you find traces of some conditional resource building - this felt particularly useful for when
focusing work on a specific subset of resources. If you know your system well, you may not need to start
&lt;em&gt;all&lt;/em&gt; services to check on specific aspects of your application. Aspire will allow you to conditionally initiate
and reference resources, since, at the end of the day, it is just c# code.
&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;p&gt;We can then reference the connection string from the project that needs it. The connection string will appear
in the system as a connect string with the name given to the resource (&lt;code&gt;GraphDbConnectionString.Name&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_2&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/GraphAccess.cs&amp;quot; start={23} end={27} /&amp;gt;&lt;/p&gt;
&lt;p&gt;Where things went awry for me is that the documentation of Arcade DB for &lt;a href=&quot;https://docs.arcadedb.com/#docker&quot;&gt;running it in a container&lt;/a&gt; says that you should bind
a specific folder in order to have the database files stored beyond container restarts. However, the graph db-related
plugin defined its own specific location that was not bound to an external volume and hence all data created while
having the solution running was lost after a restart.
To counter this behavior I made a copy of the original properties file governing the behavior of the gremlin db plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker cp ahead_graphdb:/home/arcadedb/config/gremlin-server.properties ./gremlin-server.properties
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then the file is adapted for the relevant setting to point to something in the folder
already bound for database data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;gremlin.graph=com.arcadedb.gremlin.ArcadeGraph
gremlin.arcadedb.directory=/data/graph
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Info&amp;gt;&lt;/p&gt;
&lt;p&gt;If you wanted to have a look into the container&apos;s file system,
you can do an interactive session with the container like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker exec -it ahead_graphdb sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;provided you gave it the name &lt;code&gt;ahead_graphdb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;p&gt;The readme of the solution contains instructions if you want to connect to the database via an interactive console.&lt;/p&gt;
&lt;h2&gt;The usage&lt;/h2&gt;
&lt;p&gt;The basic abstractions to use the DB follow those that we established many years ago at ahead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IAheadGraphDatabase
{
    public Task RunJob(IGremlinJob job);
    public Task&amp;lt;T&amp;gt; RunJob&amp;lt;T&amp;gt;(IGremlinJob&amp;lt;T&amp;gt; job);
}

public interface IGremlinJob
{
    Task Run(IGraphContext graphContext);
}

public interface IGremlinJob&amp;lt;T&amp;gt;
{
    Task&amp;lt;T&amp;gt; Run(IGraphContext graphContext);
}

public interface IGraphContext
{
    Task Run(string query);
    Task&amp;lt;IReadOnlyList&amp;lt;TOut&amp;gt;&amp;gt; Run&amp;lt;TIn,TOut&amp;gt;(Func&amp;lt;GraphTraversalSource,GraphTraversal&amp;lt;TIn,TOut&amp;gt;&amp;gt; query);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This forces the packaging of database mutating &amp;amp; querying as &lt;em&gt;jobs&lt;/em&gt;, where the constructor plays the role of accepting
necessary parameters and the return value typically provides DTOs that are already useful for further processing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;GraphTraversalSource&lt;/code&gt; and other Types come from the &lt;a href=&quot;https://www.nuget.org/packages/Gremlin.Net&quot;&gt;Gremlin.NET Nuget package&lt;/a&gt;,
that also comes with the methods to write a so-called traversal (aka query).&lt;/p&gt;
&lt;p&gt;&amp;lt;Info&amp;gt;
Interestingly, I was able to use the latest version of Gremlin.NET, which is still not allowed to be used with Cosmos DB.
This latest version allows eg to send the traversal request in a binary form, which presumably is more efficient to en- &amp;amp; decode than the
usual JSON.
&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;p&gt;A simple usage example is implemented in the solution in the &amp;quot;Graph&amp;quot;-page:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_2&amp;quot; file=&amp;quot;Ahead.Web/Pages/Graph.cshtml.cs&amp;quot; start={21} end={31} /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After some searching and back &amp;amp; forth, I have a somewhat better feeling about how a migration could look like.
Scaling could work with Arcade DB&apos;s clustering features or lean into things we&apos;ve learned around using different
resources for different tenants in order to support different data residencies in order to scale horizontally.&lt;/p&gt;
&lt;p&gt;Even so, I am still thinking how a migration to a document database could look like - simply for reasons of using even more
&lt;em&gt;boring tech™&lt;/em&gt;, something a long-lived product can profit immensely from.&lt;/p&gt;
</content:encoded></item><item><title>☁️ Agnostic – Intermezzo: Insightful spans with OpenTelemetry</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Fri, 02 May 2025 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;TopicToc
title=&amp;quot;Cloud agnostic series&amp;quot;
topicId=&amp;quot;azure-ahead&amp;quot;
active={frontmatter.title}
closed
/&amp;gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s take a short break from looking at service replacements and marvel at the fun that is having good &lt;a href=&quot;https://opentelemetry.io&quot;&gt;OpenTelemetry&lt;/a&gt;
data.&lt;/p&gt;
&lt;p&gt;When we started ahead many years, OpenTelemetry didn&apos;t exist yet. Aspire leans into it quite nicely,
providing a dashboard in which captured open telemetry data is seen.&lt;/p&gt;
&lt;p&gt;When you start a project from scratch, you may miss exposing open telemetry from your project. This is done through
some adequate service registrations, which in the solution I have galvanized into
a &lt;code&gt;IHostApplicationBuilder&lt;/code&gt; extension method to &lt;a href=&quot;https://github.com/flq/ahead-dockerized/blob/main/Ahead.Common/OTelConfiguration.cs&quot;&gt;be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Much of what is encoded there comes directly from the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-otlp-example&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next challenge was seeing traces that span Frontend and Backend activities
when it is correct to do so.&lt;/p&gt;
&lt;p&gt;I wasn&apos;t quite sure &lt;a href=&quot;https://github.com/dotnet/aspire/issues/583&quot;&gt;what is going on&lt;/a&gt; with OTel support around RabbitMQ, so I decided to do the
necessary boilerplate myself.&lt;/p&gt;
&lt;p&gt;What I do on the sending side e.g. when enqueueing a message is to create a new &lt;code&gt;Activity&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed showHint repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/QueueSender.cs&amp;quot; start={12} end={13} /&amp;gt;&lt;/p&gt;
&lt;p&gt;Let us revisit the sending of the message to RabbitMQ. We will send telemetry information alongside the message in the &lt;strong&gt;headers&lt;/strong&gt; of the message:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/QueueSender.cs&amp;quot; start={25} end={35} /&amp;gt;&lt;/p&gt;
&lt;p&gt;What happens in &lt;code&gt;TryInjectTraceContextIntoDictionary&lt;/code&gt;? Let&apos;s take a look:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Common/OTelUtilities.cs&amp;quot; start={13} end={20} /&amp;gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;TraceContextPropagator&lt;/code&gt; is a helper class from the &lt;strong&gt;OpenTelemetry&lt;/strong&gt; package(s). It helps to inject the proper
information into the headers of the message which we will use on the &lt;em&gt;&amp;quot;other side&amp;quot;&lt;/em&gt; to attach ourselves to the correct trace context:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/QueueListener.cs&amp;quot; start={29} end={36} /&amp;gt;&lt;/p&gt;
&lt;p&gt;This allows us to get traces that span our projects and provide us additional insights into what is going on:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/cloud-agnostic-5.png&quot; alt=&quot;Screenshot of Aspire OpenTelemetry trace of a queueing operation&quot;&gt;
&amp;lt;figcaption&amp;gt;Aspire OpenTelemetry trace of a queueing operation&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;Essentially the same was done for the Broadcasting part, which allows us to see who consumed a particular broadcast message:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/cloud-agnostic-6.png&quot; alt=&quot;Screenshot of Aspire OpenTelemetry trace of a publish operation&quot;&gt;
&amp;lt;figcaption&amp;gt;Aspire OpenTelemetry trace of a publish operation&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;When having a solution spanning multiple processes these insights are extremely valuable in understanding the internal workings of
the machine you&apos;re creating – a machine that will inevitably increase in complexity as your product grows to be successful with your customers.&lt;/p&gt;
</content:encoded></item><item><title>☁️ Agnostic – Messaging between servers with RabbitMQ (Publish/Subscribe)</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Fri, 25 Apr 2025 21:20:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;TopicToc
title=&amp;quot;Cloud agnostic series&amp;quot;
topicId=&amp;quot;azure-ahead&amp;quot;
active={frontmatter.title}
closed
/&amp;gt;&lt;/p&gt;
&lt;p&gt;Time to look at the second messaging pattern. A message can have the notion of an &lt;em&gt;event&lt;/em&gt;,
where you want several systems to be &lt;em&gt;notified&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;RabbitMQ&apos;s approach here is to declare an &lt;strong&gt;Exchange&lt;/strong&gt;. Every participant interested in messages posted to said exchange
create a &lt;strong&gt;non-persistent, temporary queue&lt;/strong&gt; tied to said exchange and dequeue incoming messages to handle them.&lt;/p&gt;
&lt;p&gt;The following sequence diagram shows what happens, based on the example outlined in the &lt;a href=&quot;https://github.com/flq/ahead-dockerized&quot;&gt;Ahead.Dockerized solution&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Web
    participant RabbitMQ
    participant Backend

    activate Backend
    activate Web
    Web-&amp;gt;&amp;gt;RabbitMQ: DeclareExchange(E)
    deactivate Web
    create participant NotificationGenerator
    Backend -&amp;gt;&amp;gt; NotificationGenerator: IHostedService
    NotificationGenerator-&amp;gt;&amp;gt;RabbitMQ: DeclareExchange(E)
    NotificationGenerator-&amp;gt;&amp;gt;RabbitMQ: DeclareTemporaryQueue(E)
    create participant SearchIndexUpdater
    Backend -&amp;gt;&amp;gt; SearchIndexUpdater: IHostedService
    deactivate Backend

    SearchIndexUpdater-&amp;gt;&amp;gt;RabbitMQ: DeclareExchange(E)
    SearchIndexUpdater-&amp;gt;&amp;gt;RabbitMQ: DeclareTemporaryQueue(E)

    activate Web
    Web-&amp;gt;&amp;gt;RabbitMQ: Publish(E, M);
    deactivate Web
   
    RabbitMQ-&amp;gt;&amp;gt;NotificationGenerator: Enqueue(M, Temporary Queue)
    activate NotificationGenerator
    NotificationGenerator-&amp;gt;&amp;gt;NotificationGenerator: Handle message
    deactivate NotificationGenerator
    RabbitMQ-&amp;gt;&amp;gt;SearchIndexUpdater: Enqueue(M, Temporary Queue)
    activate SearchIndexUpdater
    SearchIndexUpdater-&amp;gt;&amp;gt;SearchIndexUpdater: Do work
    deactivate SearchIndexUpdater
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figcaption&amp;gt;
In real life, our &lt;em&gt;&amp;quot;Backend&amp;quot;&lt;/em&gt; is an azure functions host project. As such, it comes with many affordances
like easy binding to queues on a function/endpoint level.
This is represented here by &lt;code&gt;IHostedService&lt;/code&gt; instances that run throughout the lifetime of the process.
&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;The so-called &lt;code&gt;BroadcastSender&lt;/code&gt; is fairly similar to the queue sender:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class BroadcastSender(IConnection connection, ILogger&amp;lt;BroadcastSender&amp;gt; logger)
{
    public async Task Send&amp;lt;T&amp;gt;(string broadcastExchange, T message)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we declare the exchange to be used:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed showHint repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/BroadcastSender.cs&amp;quot; start={15} end={15} /&amp;gt;&lt;/p&gt;
&lt;p&gt;And use the channel once more to publish, this time with a specific &lt;code&gt;PublicationAddress&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/BroadcastSender.cs&amp;quot; start={26} end={29} /&amp;gt;&lt;/p&gt;
&lt;p&gt;This class is then used in the following way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;routeBuilder.MapGet(&amp;quot;/publish&amp;quot;, async (BroadcastSender sender, Random random) =&amp;gt;
{
    await sender.Send(
        Constants.BroadcastExchanges.UserEvents,
        new PagePublishedEvent(random.Next(1, 1000).ToString()));
    return Results.Extensions.BackToHomeWithMessage(&amp;quot;Page has been published!&amp;quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;Backend&lt;/code&gt; the &lt;code&gt;BroadcastListener&lt;/code&gt; also exposes a method to obtain an &lt;code&gt;AsyncEnumerable&lt;/code&gt; (done again with a &lt;code&gt;Channel&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The one major difference is the way we set up the queue that is being used.&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/BroadcastListener.cs&amp;quot; start={22} end={26} /&amp;gt;&lt;/p&gt;
&lt;p&gt;declaring a queue without any parameters basically makes this a transient queue, with a randomly assigned name which
we can pick up via &lt;code&gt;tmpQueue.QueueName&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Via injection we can then consume the same message in multiple places, for example like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class SearchIndexUpdater(
    BroadcastListener&amp;lt;PagePublishedEvent&amp;gt; eventBroadcast, 
    ILogger&amp;lt;SearchIndexUpdater&amp;gt; logger) : IHostedService
{
    public Task StartAsync(CancellationToken token)
    {
        logger.LogInformation(&amp;quot;Starting Search index updater&amp;quot;);
        _ = Task.Run(async () =&amp;gt;
        {
            await foreach (var publishedEvent in eventBroadcast.StartListening(
                    Constants.BroadcastExchanges.UserEvents, 
                    nameof(SearchIndexUpdater), token))
                logger.LogInformation(
                    &amp;quot;Received page published event for page id {pageId}, will update the search index&amp;quot;,
                    publishedEvent.PageId);
        }, token);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken) =&amp;gt; Task.CompletedTask;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With those infrastructure pieces in place, we leave the lovely world of message-passing with RabbitMQ.&lt;/p&gt;
</content:encoded></item><item><title>☁️ Agnostic - Messaging between servers with RabbitMQ (Queueing)</title><link>https://realfiction.net/posts/undefined/</link><guid isPermaLink="true">https://realfiction.net/posts/undefined/</guid><pubDate>Thu, 24 Apr 2025 21:20:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;TopicToc
title=&amp;quot;Cloud agnostic series&amp;quot;
topicId=&amp;quot;azure-ahead&amp;quot;
active={frontmatter.title}
closed
/&amp;gt;&lt;/p&gt;
&lt;p&gt;Sending messages between processes is an ubiquitous pattern that has been in use for many decades - as such,
the available alternatives are plenty.&lt;/p&gt;
&lt;p&gt;In the past I have only heard good things about &lt;a href=&quot;https://www.rabbitmq.com&quot;&gt;RabbitMQ&lt;/a&gt;. What I did not know before I looked at it is
that it is able to cover both our Inter-process communication (&lt;strong&gt;IPC&lt;/strong&gt;) requirements by using queues as well as providing
a publish / subscribe model.&lt;/p&gt;
&lt;h2&gt;RabbitMQ as an Aspire resource&lt;/h2&gt;
&lt;p&gt;Support to use RabbitMQ within an Aspire solution is done via the &lt;code&gt;Aspire.Hosting.RabbitMQ&lt;/code&gt; nuget package:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed showHint repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;AppHost/Program.cs&amp;quot; start={20} end={22} /&amp;gt;&lt;/p&gt;
&lt;p&gt;This references two secrets that are defined as &lt;code&gt;ParameterResource&lt;/code&gt;s:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;AppHost/Program.cs&amp;quot; start={6} end={7} /&amp;gt;&lt;/p&gt;
&lt;p&gt;All of this gets referenced by our two projects &lt;strong&gt;Web&lt;/strong&gt; and &lt;strong&gt;Backend&lt;/strong&gt; (here exemplified with the Backend project).&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;AppHost/Program.cs&amp;quot; start={33} end={37} /&amp;gt;&lt;/p&gt;
&lt;p&gt;I only learned of &lt;code&gt;WaitFor&lt;/code&gt; a little bit later - which is a very practical way to ensure
that an infrastructure resource like RabbitMQ is already available once our own code starts running.&lt;/p&gt;
&lt;p&gt;For our playground, I chose sending a queue message for triggering the &lt;strong&gt;making of a report&lt;/strong&gt; and a &lt;strong&gt;page published&lt;/strong&gt; &lt;em&gt;event&lt;/em&gt;
that in our real system is handled by multiple consumers to perform a number of tasks of which two here are exemplified.&lt;/p&gt;
&lt;p&gt;&amp;lt;Info&amp;gt;&lt;/p&gt;
&lt;h3&gt;A word about RabbitMQ&lt;/h3&gt;
&lt;p&gt;From the time I&apos;ve had to look into RabbitMQ, it appears to be a Swiss army knife of messaging.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Queues can take many forms (temporary or well-known, persistent or transient)&lt;/li&gt;
&lt;li&gt;Exchanges can be defined and on top of this different strategies can be used to get the message as often and to as many locations as necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have IPC demands maybe even between processes written in different programming languages, I suggest that you take a look at RabbitMQ,
chances are that it will be able to cover your particular scenarios as it provides clients for a multitude of different environments.&lt;/p&gt;
&lt;p&gt;The relevant .NET-specific documentation &lt;a href=&quot;https://www.rabbitmq.com/tutorials/tutorial-one-dotnet&quot;&gt;can be found here&lt;/a&gt;.
&amp;lt;/Info&amp;gt;&lt;/p&gt;
&lt;h2&gt;Using queues&lt;/h2&gt;
&lt;p&gt;Queues are a very useful pattern to offload work to a different process as well as helping to ensure that your
compute resources don&apos;t get overwhelmed, as you have control over how many &amp;quot;workers&amp;quot; may dequeue messages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/cloud-agnostic-4.png&quot; alt=&quot;Aspire Dashboard showing the known resources&quot;&gt;
&amp;lt;figcaption&amp;gt;Queues as a way to pass a message to a different process and control the workload at any given time&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;When using RabbitMQ, we should declare the queue we want to use from &amp;quot;both sides&amp;quot;, after which we can perform en- &amp;amp; dequeue operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Web
    participant RabbitMQ
    participant Backend

    activate Backend
    Backend-&amp;gt;&amp;gt;RabbitMQ: DeclareQueue(Q)
    activate RabbitMQ
    RabbitMQ--&amp;gt;&amp;gt;Backend: ok
    deactivate RabbitMQ
    deactivate Backend

    activate Web
    Web-&amp;gt;&amp;gt;RabbitMQ: DeclareQueue(Q)
    activate RabbitMQ
    RabbitMQ--&amp;gt;&amp;gt;Web: ok
    Web-&amp;gt;&amp;gt;RabbitMQ: Publish(M)
    activate RabbitMQ
    deactivate Web
    RabbitMQ-&amp;gt;&amp;gt;Backend: Deliver(M)
    activate Backend
    deactivate RabbitMQ
    deactivate Backend
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figcaption&amp;gt;Declaring Queues is an idempotent operation and is used by &amp;quot;both sides&amp;quot; to ensure a queue&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;In order to use RabbitMQ, I used the &lt;code&gt;Aspire.RabbitMQ.Client.v7&lt;/code&gt; Nuget, and ensure that it gets set up correctly
by adding it to the Services in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder.AddRabbitMQClient(connectionName: &amp;quot;messaging&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name &lt;code&gt;messaging&lt;/code&gt; is related to how we defined the resource in the &lt;strong&gt;Aspire&lt;/strong&gt; project (see above). By letting our projects
reference the resource, the correct information to correctly set up a client is injected into our projects.&lt;/p&gt;
&lt;h2&gt;Enqueueing&lt;/h2&gt;
&lt;p&gt;Ahead.Web registers a &lt;code&gt;QueueSender&lt;/code&gt; at startup with the following shape:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class QueueSender(IConnection connection, ILogger&amp;lt;QueueSender&amp;gt; logger) {
    public async Task Send&amp;lt;T&amp;gt;(string queueName, T message)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty much everything we want to do with RabbitMQ appears to have to be done with a &lt;code&gt;Channel&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/QueueSender.cs&amp;quot; start={15} end={21} /&amp;gt;&lt;/p&gt;
&lt;p&gt;In this demo solution I did not bother making anything from RabbitMQ persistent, hence we do not have to ask for persistence guarantees.
Note that…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An exclusive queue can only be used (consumed from, purged, deleted, etc) by its declaring connection&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;so that would be the wrong thing to use here. The passed in message gets serialized to a &lt;strong&gt;byte array&lt;/strong&gt;
(by serializing the message as JSON stored in a variable named &lt;code&gt;body&lt;/code&gt;) and sent to the queue:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Web/Infrastructure/QueueSender.cs&amp;quot; start={32} end={35} /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Dequeueing&lt;/h2&gt;
&lt;p&gt;A similar helper, but for dequeueing, is defined in the &lt;code&gt;Ahead.Backend&lt;/code&gt; project and looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class QueueListener&amp;lt;T&amp;gt;(IConnection connection, ILogger&amp;lt;QueueListener&amp;lt;T&amp;gt;&amp;gt; logger)
{
    public async IAsyncEnumerable&amp;lt;T&amp;gt; StartListening(
        string queueName,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;IAsyncEnumerable&lt;/code&gt; needs to be built with the aid of the RabbitMQ client library and the trusty
&lt;code&gt;Channel&lt;/code&gt; abstraction that .NET provides:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/QueueListener.cs&amp;quot; start={18} end={22} /&amp;gt;&lt;/p&gt;
&lt;p&gt;Once more we declare the queue in much the same way as we did on the sending part.&lt;/p&gt;
&lt;p&gt;Then we build a so-called consumer that allows us to provide a callback when something is being received:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/QueueListener.cs&amp;quot; start={28} end={29} /&amp;gt;
&amp;lt;figcaption&amp;gt;I am trying to remember why I called the main input to the handling callback &amp;quot;ea&amp;quot; – bad name!&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;At the core of the handler we deserialize the message and pass it to the Writer part of a &lt;code&gt;Channel&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/QueueListener.cs&amp;quot; start={37} end={43} /&amp;gt;&lt;/p&gt;
&lt;p&gt;In order to satisfy the signature of the method, we use the &lt;code&gt;Reader&lt;/code&gt; part of the &lt;code&gt;Channel&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;GHEmbed repo=&amp;quot;ahead-dockerized&amp;quot; branch=&amp;quot;snapshot_1&amp;quot; file=&amp;quot;Ahead.Backend/Infrastructure/QueueListener.cs&amp;quot; start={52} end={57} /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;line 52&lt;/em&gt; also shows how the consumer is associated with the particular queue name.&lt;/p&gt;
&lt;p&gt;Given all this Infrastructure we can enqueue a message like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;routeBuilder.MapGet(&amp;quot;/report&amp;quot;, async (QueueSender sender, TimeProvider timeProvider) =&amp;gt;
{
    await sender.Send(
        Constants.QueueNames.Basic,
        new ReportRequest
        {
            Type = &amp;quot;onboarding&amp;quot;,
            RelevantDate = timeProvider.GetUtcNow().Date
        });

    return Results.Extensions.BackToHomeWithMessage(&amp;quot;Report sent!&amp;quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and dequeue it as&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await foreach (var reportRequest in queue.StartListening(Constants.QueueNames.Basic, cancellationToken))
                logger.LogInformation(
                    &amp;quot;Report request of type {reportType} received for date {reportDate}&amp;quot;, 
                    reportRequest.Type,
                    reportRequest.RelevantDate);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s put a ✅ on queueing, next will be publishing messages to which others may subscribe.&lt;/p&gt;
</content:encoded></item></channel></rss>