<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ken Bonny's dotnet, Azure, architecture and feature testing focussed blog]]></title><description><![CDATA[Software craftsman specialising in Microsoft technology]]></description><link>https://kenbonny.net</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 01:18:54 GMT</lastBuildDate><atom:link href="https://kenbonny.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Closing thoughts on A-Frame architecture]]></title><description><![CDATA[Phew, that was a lot. I'm always surprised how simple solutions contain so much detail and nuance when I try to explain them. I hope I conveyed the message that A-Frame simplifies code within a module or class by separating the infrastructure compone...]]></description><link>https://kenbonny.net/closing-thoughts-on-a-frame-architecture</link><guid isPermaLink="true">https://kenbonny.net/closing-thoughts-on-a-frame-architecture</guid><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sun, 10 Aug 2025 06:00:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320217215/1a535fef-40b8-4d0e-8765-7a7ccbfc95ee.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Phew, that was a lot. I'm always surprised how simple solutions contain so much detail and nuance when I try to explain them. I hope I conveyed the message that A-Frame simplifies code within a module or class by separating the infrastructure components and logic components. They will interact with each other through a controller that knows how to connect one to the other. Keep logic focused on the feature and try to keep infrastructure as straightforward as possible.</p>
<p>Good luck out there!</p>
<h2 id="heading-sources">Sources</h2>
<p><a target="_blank" href="https://jeremydmiller.com/2023/07/19/a-frame-architecture-with-wolverine/">A-Frame Architecture with Wolverine</a></p>
<p><a target="_blank" href="https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#a-frame-arch">James Shore A-Frame Architecture</a></p>
<p><a target="_blank" href="https://wolverine.netlify.app/tutorials/">Wolverine Docs</a></p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests">Integration testing in dotnet</a></p>
<p><a target="_blank" href="https://tunit.dev">TUnit</a></p>
]]></content:encoded></item><item><title><![CDATA[Testing A-Frame architecture]]></title><description><![CDATA[No architecture is complete without an easy way to test the functionality. My recommended strategy is to use two types of tests: unit and integration.
As far as test setup goes, there is no clear winner for a test framework. xUnit.NET, NUnit and the ...]]></description><link>https://kenbonny.net/testing-a-frame-architecture</link><guid isPermaLink="true">https://kenbonny.net/testing-a-frame-architecture</guid><category><![CDATA[T Unit]]></category><category><![CDATA[TUnit]]></category><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sun, 03 Aug 2025 06:00:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320200528/e83794dd-8a67-48ce-82f4-1a32d6cdb84d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>No architecture is complete without an easy way to test the functionality. My recommended strategy is to use two types of tests: unit and integration.</p>
<p>As far as test setup goes, there is no clear winner for a test framework. <a target="_blank" href="http://xUnit.NET">xUnit.NET</a>, <a target="_blank" href="https://nunit.org">NUnit</a> and the newcomer <a target="_blank" href="https://tunit.dev">TUnit</a> are all dependable frameworks. Personally, I like xUnit for their terminology with Fact and Theory, yet the newcomer TUnit is quickly capturing my interest with some <a target="_blank" href="https://tunit.dev/docs/comparison/attributes#test-control-attributes">interesting features</a>. I've used it here to encourage everybody (including me) to keep experimenting and learning.</p>
<p>I have similar thoughts on assertion and mocking libraries. There are no bad choices. They’re just tools to get the job done. Pick one and use it as long as it is useful.</p>
<h3 id="heading-unit-tests">Unit tests</h3>
<p>The logic code is the easiest to test with standard unit tests. These tests will verify all scenarios that the software needs to support. Since there is no infrastructure setup required, these tests are easy to read and fairly straightforward. Since unit tests are pretty cheap (quick) to run, I’ll write a lot of them to cover all scenarios.</p>
<p>Instantiating the system under test is nothing more than creating a new instance of the class. Here I see the first A-Frame benefit: by limiting the number of services to inject, I can simplify my test setup. When there are no systems to prepare and ensure they return the correct data in the correct circumstances, it takes a gigantic load off my shoulders when preparing my tests.</p>
<p>All injections happen in the <code>Handle</code> function. Keep injected data simple and specific to the case under test. In this case, I create a default <code>_walk</code> that I pass to the function or that can serve as an <a target="_blank" href="https://martinfowler.com/bliki/ObjectMother.html">object mother</a> that I modify to the needs of the test. The <code>_otherWalk</code> is similar to the <code>_walk</code> object, but with another dog that crossed paths with ours. The <code>Func&lt;byte[]&gt;</code> is an easily stubbed method that captures whether the function has been called. Should the need arise, returning different data is now trivial.</p>
<p>While the date is irrelevant for this code, it is a good example to keep mutable calls out of the logic component. Testing with specific dates has been a challenge in the past as there could be a hardcoded <a target="_blank" href="http://DateTimeOffset.Now"><code>DateTimeOffset.Now</code></a> present. It is possible to replace that with the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider"><code>TimeProvider</code></a>. I prefer simply passing the date to the test instead of setting up another system.</p>
<p>The result of the logic tells me what it expects to happen. I don't need to start checking multiple mocks and stubs, simple asserts are enough. While there is a mock here, it is only one and not multiple. I find that the result is transparent to interpret.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">When_other_dog_encountered_then_do_indicate_dog_encountered</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> getPictureCalled = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">var</span> (result, outgoingMessages, entityFrameworkInsert) = <span class="hljs-keyword">new</span> MetFriendsHandler().Handle(
        _walk,
        [<span class="hljs-meta">_otherWalk</span>],
        () =&gt;
        {
            getPictureCalled = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">return</span> [];
        },
        DateTimeOffset.Now);

    <span class="hljs-keyword">await</span> Assert.That(result).IsEquivalentTo(Results.Ok(<span class="hljs-keyword">new</span> FriendsResponse([<span class="hljs-string">"Toby"</span>], [])));
    <span class="hljs-keyword">var</span> friends = outgoingMessages.ShouldHaveMessageOfType&lt;MetFriends&gt;().Friends;
    <span class="hljs-keyword">await</span> Assert.That(friends).IsNotEmpty().And.Contains(<span class="hljs-string">"Toby"</span>);
    <span class="hljs-keyword">await</span> Assert.That(entityFrameworkInsert).IsNotNull();
    <span class="hljs-keyword">await</span> Assert.That(getPictureCalled).IsTrue();
}
</code></pre>
<h3 id="heading-integration-tests">Integration tests</h3>
<p>Now that I've tackled the easy part, let's look at the complex part. Infrastructure code is not easy to test, no matter how I twist or turn it. I've tried mocking it out, I've tried using the Entity Framework in-memory database, I've tried sacrificing managers to the god <a target="_blank" href="https://en.wikipedia.org/wiki/Maniae">Maniae</a>. This is where integration tests shine. These tests verify how your system responds to actual external system behaviour. They're slower than unit tests, so focus on the most critical scenarios while using unit tests for edge cases.</p>
<p>Integration tests run (as close to) actual systems. This means starting your application in a web server, talking to a real database and sending requests to external systems over the network. To run the web server in-memory, I use the <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests">WebApplicationFactory</a>. This is what I did in my basic (and honestly, quite useless) integration test. In more complex scenarios, I use a library such as <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests">Alba</a> or <a target="_blank" href="https://playwright.dev/dotnet/">Playwright</a>.</p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli">Don't try to simulate the database</a>. I create a local test database in a container or spin up a lightweight database in my CI/CD pipeline. <a target="_blank" href="https://testcontainers.com">Test containers</a> can be quite convenient, but they take some time to start. I run my migration scripts, then I test against that database. This way, I ensure that my migration scripts work on the database. Another system that works as expected. To reset a database to a known good point, I use <a target="_blank" href="https://github.com/jbogard/Respawn">Respawn</a>. I prefer resetting the database before each test run. This ensures a clean database before each test, which populates it with just the necessary data and eliminates a previous test from influencing another. After a test fails, I have access to the data to debug efficiently.</p>
<p>Write to the local file system when integration testing. In a CI/CD environment, my tests run in a container that gets disposed of afterwards. This is a good environment to try writes as the data gets discarded after every run. I can even publish the test output as an artefact if I want to inspect it afterwards.</p>
<p>The only time I mock, fake or stub interfaces is when I work with external services. To get realistic responses, I call each system with test data. I prefer doing this with a test system, but I will use the real API if I have no other option. In the last case, I'll never do that unannounced. I'll get in touch with the external services team to coordinate a moment and specify which data I’ll send. In all cases I keep the requests and the responses. I use both good and bad responses in my integration tests, so my system is prepared for all possible scenarios.</p>
<p>Practically, I try to mock, fake or stub the <code>HttpClient</code> to get control of its return values. <a target="_blank" href="http://WireMock.NET">WireMock.NET</a> can come in quite handy in these scenarios. If I use libraries such as <a target="_blank" href="https://reactiveui.github.io/refit/">Refit</a>, <a target="_blank" href="https://restsharp.dev">RestSharp</a> or <a target="_blank" href="https://flurl.dev">Flurl</a>, I'll use their built-in test support.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ClassDataSource&lt;WebAppFactory&gt;(Shared = SharedType.PerTestSession)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">MetFriendsIntegrationTests</span>(<span class="hljs-params">WebAppFactory webAppFactory</span>)</span>
{
    [<span class="hljs-meta">Test</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Get_response_bad_request</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> client = webAppFactory.CreateClient();

        <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> client.GetAsync(<span class="hljs-string">"/friends/1"</span>);

        <span class="hljs-keyword">await</span> Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WebAppFactory</span> : <span class="hljs-title">WebApplicationFactory</span>&lt;<span class="hljs-title">Program</span>&gt;, <span class="hljs-title">IAsyncInitializer</span>
{
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureWebHost</span>(<span class="hljs-params">IWebHostBuilder builder</span>)</span>
    {
        <span class="hljs-comment">// let Oakton accept --environment variables</span>
        OaktonEnvironment.AutoStartHost = <span class="hljs-literal">true</span>;

        <span class="hljs-comment">// disable all external setup so the integration tests don't start sending out messages</span>
        builder.ConfigureTestServices(services =&gt; services.DisableAllExternalWolverineTransports());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">InitializeAsync</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Grab a reference to the server</span>
        <span class="hljs-comment">// This forces it to initialise.</span>
        <span class="hljs-comment">// By doing it within this method, it's thread safe.</span>
        <span class="hljs-comment">// And avoids multiple initialisations from different tests if parallelisation is switched on</span>
        _ = Server;
        <span class="hljs-keyword">return</span> Task.CompletedTask;
    }
}
</code></pre>
<p>Next up, the last post in this series: my closing thoughts on A-Frame architecture.</p>
]]></content:encoded></item><item><title><![CDATA[Tackling complex examples using A-Frame architecture and Wolverine]]></title><description><![CDATA[The examples in the previous posts seem really nice for simple scenarios. How do I approach more advanced use cases? The big scenarios are:

I need multiple pieces of data from different sources

I need to perform an infrastructure call in the middle...]]></description><link>https://kenbonny.net/tackling-complex-examples-using-a-frame-architecture-and-wolverine</link><guid isPermaLink="true">https://kenbonny.net/tackling-complex-examples-using-a-frame-architecture-and-wolverine</guid><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Wolverine]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sat, 26 Jul 2025 22:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320177682/5139137e-d252-4802-ba1d-1fea0ab4f687.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The examples in the previous posts seem really nice for simple scenarios. How do I approach more advanced use cases? The big scenarios are:</p>
<ol>
<li><p>I need multiple pieces of data from different sources</p>
</li>
<li><p>I need to perform an infrastructure call in the middle of a logic component</p>
</li>
<li><p>I need to do different infrastructure calls based on the decisions taken by my logic code</p>
</li>
</ol>
<h3 id="heading-1-multiple-pieces-of-infrastructure-data">1. Multiple pieces of infrastructure data</h3>
<p>When multiple objects need to be loaded, the <code>Load</code> method can return a tuple. Wolverine injects each object separately in the <code>Validate</code> and <code>Handle</code> methods. The <code>Load</code> function returns the dogs from the database, an image from the web and the current date from the system clock as a <code>Tuple</code>. Wolverine will inject the list of dogs, the picture and the date correctly into the <code>Validate</code> and <code>Handle</code> methods. If different functions require different objects, they only need to specify which data they require. Wolverine will even resolve the services such as the <code>IWatermarkService</code> from the dependency injection framework.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;(List&lt;Dog&gt; dogs, Image picture, DateTimeOffset now)&gt; LoadAsync(<span class="hljs-comment">/* dependencies go here */</span>) 
{
    <span class="hljs-comment">// infrastructure code goes here</span>
    <span class="hljs-keyword">return</span> (dogsFromDatabase, pictureFromTheWeb, DateTimeOffset.Now);
}

<span class="hljs-function"><span class="hljs-keyword">public</span> ProblemDetails <span class="hljs-title">Validate</span>(<span class="hljs-params">List&lt;Dog&gt; dogs, DateTimeOffset now</span>)</span>
{
    <span class="hljs-comment">// validation code goes here</span>
    <span class="hljs-keyword">return</span> WolverineContinue.NoProblems;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> DogDto <span class="hljs-title">Handle</span>(<span class="hljs-params">List&lt;Dog&gt; dogs, Image picture, DateTimeOffset now, IWatermarkService watermark</span>)</span>
{
    <span class="hljs-comment">// logic code goes here</span>
}
</code></pre>
<h3 id="heading-2-infrastructure-calls-in-the-middle-of-logic-code">2. Infrastructure calls in the middle of logic code</h3>
<p>The easiest solution is to avoid this scenario. Try to structure the logic code differently so that the infrastructure code can load all data that upfront.</p>
<p>There are situations where I don't want to incur the upfront cost. For example, when there is a chance that the data isn't necessary and the load puts the system under stress. In such cases, I can inject infrastructure code into the logic component. This does not need to mean that I'm back to square one of injecting an interface or <code>DbContext</code> into my <code>Handle</code> method.</p>
<p><code>Func&lt;&gt;</code> can be returned from the <code>Load</code> method. Thus delegating the execution to a later time. I get the benefits that the infrastructure code prepares the call, and I get an immutable way of testing my logic as I can replace the <code>Func&lt;&gt;</code> with a simple test stub.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span>
    Task&lt;(WalkWithDogs? Walk, List&lt;WalkWithDogs&gt; OtherWalks, Func&lt;<span class="hljs-keyword">byte</span>[]&gt; GetPictureAsync, DateTimeOffset Now)&gt;
    LoadAsync(<span class="hljs-keyword">int</span> walkId, DogWalkingContext db)
{
    <span class="hljs-keyword">var</span> walk = <span class="hljs-keyword">await</span> db.WalksWithDogs.Include(w =&gt; w.Dogs).FirstOrDefaultAsync(w =&gt; w.Id == walkId);
    <span class="hljs-keyword">var</span> dogsInWalk = walk?.Dogs.Select(d =&gt; d.Id).ToArray() ?? [];
    <span class="hljs-keyword">var</span> otherWalks = <span class="hljs-keyword">await</span> db.WalksWithDogs.Include(w =&gt; w.Dogs)
        .Where(w =&gt; !w.Dogs.Any(d =&gt; dogsInWalk.Contains(d.Id)))
        .ToListAsync();
    <span class="hljs-keyword">var</span> getPicture = () =&gt;
    {
        <span class="hljs-keyword">var</span> stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(<span class="hljs-string">"MoreThanCode.AFrameExample.Yuna.jpg"</span>);
        <span class="hljs-keyword">if</span> (stream == <span class="hljs-literal">null</span>)
            <span class="hljs-keyword">return</span> [];
        stream.Seek(<span class="hljs-number">0</span>, SeekOrigin.Begin);
        <span class="hljs-keyword">byte</span>[] image = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[stream.Length];
        stream.ReadExactly(image);
        <span class="hljs-keyword">return</span> image;
    };
    <span class="hljs-keyword">return</span> (walk, otherWalks, getPicture, DateTimeOffset.Now);
}

[<span class="hljs-meta">WolverineGet(<span class="hljs-meta-string">"/friends/{walkId}"</span>, OperationId = <span class="hljs-meta-string">"Friends-On-Walk"</span>)</span>]
[<span class="hljs-meta">Tags(<span class="hljs-meta-string">"MoreThanCode.AFrameExample"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> IResult <span class="hljs-title">Handle</span>(<span class="hljs-params">
    WalkWithDogs walk,
    List&lt;WalkWithDogs&gt; otherWalksAtSameTime,
    Func&lt;<span class="hljs-keyword">byte</span>[]&gt; getPicture,
    DateTimeOffset now,
    Watermark watermarkService</span>)</span>
{
    <span class="hljs-keyword">if</span> (!otherWalksAtSameTime.Any())
        <span class="hljs-keyword">return</span> Results.Empty;

    <span class="hljs-keyword">var</span> friends = otherWalksAtSameTime.SelectMany(w =&gt; w.Dogs)
                    .Except(walk.Dogs)
                    .Select(d =&gt; d.Name)
                    .ToArray();

    FriendsResponse response = friends.Length == <span class="hljs-number">0</span> 
        ? <span class="hljs-keyword">new</span>([], []) 
        : <span class="hljs-keyword">new</span>(friends, watermarkService.Add(getPicture()));

    <span class="hljs-keyword">return</span> Results.Ok(response);
}
</code></pre>
<h3 id="heading-3-complex-return-instructions">3. Complex return instructions</h3>
<p>What if I want to save the walk back to the database, write the picture to disk and publish additional messages? For these purposes, Wolverine has <a target="_blank" href="https://wolverine.netlify.app/guide/handlers/cascading.html">cascading messages</a> and <a target="_blank" href="https://wolverine.netlify.app/guide/handlers/side-effects.html"><code>ISideEffect</code>s</a>.</p>
<p>Although there are other ways to return cascading messages, I prefer to work with the <code>OutgoingMessages</code> response type. This way I can return none, one or multiple messages based on the decisions of my logic. Wolverine will publish these messages to the bus according to the configured routing information. It's also possible to schedule or delay messages if needed. All these messages will benefit from built-in resiliency mechanisms.</p>
<p>Infrastructure operations that are part of the scope of the handler are best implemented as side effects. Common side effects are saving to the database and writing to disk. For this, there is the <code>ISideEffect</code> marker interface that expects an <code>Execute</code> function to be available. This function is not present on the interface as the input can accept parameters resolved from dependency injection, just like the <code>Handle</code> function discussed earlier. When a side effect is optional, make it nullable and return <code>null</code> if it should not happen.</p>
<p>It isn't possible to return a generic list of <code>ISideEffect</code>s. Wolverine needs to know the explicit type of the side effect to resolve the injectable parameters correctly. Side effects are part of the transaction spanning the <code>Load</code>, <code>Verify</code> and <code>Handle</code> functions. This means that if a side effect fails, the message processing fails.</p>
<p>Wolverine publishes the messages after everything in the transaction succeeds. This prevents ghost messages notifying other parts of the system of an operation that may have failed. To prevent messages from disappearing because there is a problem with the bus, I recommend enabling the outbox via <a target="_blank" href="https://wolverine.netlify.app/guide/durability/">durable messaging</a>. Storing the message in the database is part of the transaction which prevents those messages from being lost.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> (IResult, OutgoingMessages, EntityFrameworkInsert&lt;WalkWithDogs&gt;?) Handle(
    WalkWithDogs walk,
    List&lt;WalkWithDogs&gt; otherWalksAtSameTime,
    Func&lt;<span class="hljs-keyword">byte</span>[]&gt; getPicture,
    DateTimeOffset now,
    Watermark watermarkService)
{
    <span class="hljs-keyword">var</span> outgoingMessages = <span class="hljs-keyword">new</span> OutgoingMessages();

    <span class="hljs-keyword">if</span> (!otherWalksAtSameTime.Any())
        <span class="hljs-keyword">return</span> (Results.Empty, outgoingMessages, <span class="hljs-literal">null</span>);

    <span class="hljs-keyword">var</span> friends = otherWalksAtSameTime.SelectMany(w =&gt; w.Dogs).Except(walk.Dogs).Select(d =&gt; d.Name).ToArray();
    <span class="hljs-keyword">if</span> (friends.Length != <span class="hljs-number">0</span>)
        outgoingMessages.Add(<span class="hljs-keyword">new</span> MetFriends(friends));

    FriendsResponse response = friends.Length == <span class="hljs-number">0</span> 
        ? <span class="hljs-keyword">new</span>([], []) 
        : <span class="hljs-keyword">new</span>(friends, watermarkService.Add(getPicture()));

    <span class="hljs-keyword">return</span> (Results.Ok(response), outgoingMessages, <span class="hljs-keyword">new</span> EntityFrameworkInsert&lt;WalkWithDogs&gt;(walk));
}
</code></pre>
<p>A last remark: when a side effect can fail, publish an event or command and handle it. The most prevalent examples are network calls. A network call can fail for a multitude of reasons. When I trigger the call to an external system in an event, I can leverage built-in <a target="_blank" href="https://wolverine.netlify.app/guide/handlers/error-handling.html">retry</a> and <a target="_blank" href="https://wolverine.netlify.app/guide/handlers/error-handling.html">error</a> handling mechanisms. This means I have battle-tested ways of handling failures.</p>
<p>Next up, I’ll be looking into different ways of testing logic and infrastructure code.</p>
]]></content:encoded></item><item><title><![CDATA[Leverage Wolverine's A-Frame architecture support]]></title><description><![CDATA[I’ve shown how I write code using the A-Frame architecture without help. Yet I find a library or framework very convenient when using advanced techniques. Wolverine is at its core a messaging framework, but goes well beyond that. It has an in-memory ...]]></description><link>https://kenbonny.net/leverage-wolverines-a-frame-architecture-support</link><guid isPermaLink="true">https://kenbonny.net/leverage-wolverines-a-frame-architecture-support</guid><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Wolverine]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sat, 19 Jul 2025 22:00:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320233624/2c515955-a1e9-4ef6-a4c6-0c72768f04a2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’ve shown how I write code using the A-Frame architecture without help. Yet I find a library or framework very convenient when using advanced techniques. <a target="_blank" href="https://wolverine.netlify.app">Wolverine</a> is at its core a messaging framework, but goes well beyond that. It has an in-memory transport, so it behaves like a mediator, but with all the quality of life improvements that apply to any other transport. It supports durable messaging via the in- and outbox patterns, retries, timeouts, error handling and much more. The in-memory transport is great for integration testing as well. Once the app goes live, it can switch the transport out for an external system such as <a target="_blank" href="https://www.rabbitmq.com">RabbitMQ</a>, <a target="_blank" href="https://azure.microsoft.com/en-us/products/service-bus/">Azure Service Bus</a>, <a target="_blank" href="https://aws.amazon.com/sqs/">Amazon SQS</a>/<a target="_blank" href="https://aws.amazon.com/sns/">SNS</a>, <a target="_blank" href="https://kafka.apache.org">Apache Kafka</a> and even <a target="_blank" href="https://www.microsoft.com/en-us/sql-server">SQL server</a> or <a target="_blank" href="https://www.postgresql.org">PostgreSQL</a> if I have limited messaging needs. It also supports messages coming in through HTTP requests, so I can build an API with it.</p>
<p>Wolverine installs alongside other messaging and API frameworks such as <a target="_blank" href="https://masstransit.io">MassTransit</a>, <a target="_blank" href="https://particular.net/nservicebus">NServiceBus</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-9.0">minimal API</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/overview">ASP.NET</a> and <a target="_blank" href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a>. This allows gradual integration within existing projects. For this purpose, I will leave the functionality of creating and retrieving a dog in the minimal API.</p>
<p>Let me demonstrate how simple Wolverine can be with another simple example: a walk. To create a walk, I have to map the path I walked and which dogs I had with me. In a real system, I'd look for GPS integration. For this demo system I use a list of coordinates <code>[(0,0), (0,1), (2, 1), ...]</code>. I also want to verify that the dogs are in the system, so I don't have unknown dogs with me.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RegisterWalkHandler</span>
{
    <span class="hljs-keyword">public</span> Task&lt;Dog[]&gt; LoadAsync(RegisterWalk request, DogWalkingContext db) =&gt;
        db.Dogs.Where(d =&gt; request.DogsOnWalk.Contains(d.Name)).ToArrayAsync();

    <span class="hljs-function"><span class="hljs-keyword">public</span> ProblemDetails <span class="hljs-title">Validate</span>(<span class="hljs-params">RegisterWalk request, Dog[] knownDogs</span>)</span>
    {
        <span class="hljs-keyword">var</span> knownNames = knownDogs.Select(d =&gt; d.Name);
        <span class="hljs-keyword">var</span> unknownDogs = request.DogsOnWalk.Except(knownNames).ToArray();
        <span class="hljs-keyword">return</span> unknownDogs.Any()
            ? <span class="hljs-keyword">new</span> ProblemDetails
            {
                Status = (<span class="hljs-keyword">int</span>)HttpStatusCode.BadRequest,
                Title = <span class="hljs-string">"Unknown dog or dogs"</span>,
                Detail = <span class="hljs-keyword">string</span>.Join(<span class="hljs-string">", "</span>, unknownDogs)
            }
            : WolverineContinue.NoProblems;
    }

    [<span class="hljs-meta">WolverinePost(<span class="hljs-meta-string">"/walk"</span>, Name = <span class="hljs-meta-string">"Register Walk"</span>, OperationId = <span class="hljs-meta-string">"Walk"</span>)</span>]
    [<span class="hljs-meta">Tags(<span class="hljs-meta-string">"MoreThanCode.AFrameExample"</span>)</span>]
    <span class="hljs-keyword">public</span> (LazyCreationResponse&lt;WalkResponse&gt; response, EntityFrameworkInsert&lt;WalkWithDogs&gt; insertWalk) Handle(
        RegisterWalk request,
        Dog[] dogsOnWalk)
    {
        <span class="hljs-keyword">var</span> walk = <span class="hljs-keyword">new</span> WalkWithDogs
        {
            Dogs = dogsOnWalk,
            Path = request.Path.Select((coord, index) =&gt; <span class="hljs-keyword">new</span> CoordinateEntity
                {
                    X = coord.X,
                    Y = coord.Y,
                    SequenceOrder = index
                })
                .ToList()
        };

        <span class="hljs-keyword">var</span> response = () =&gt; <span class="hljs-keyword">new</span> WalkResponse(walk.Id, dogsOnWalk.Select(d =&gt; <span class="hljs-keyword">new</span> DogResponse(d)).ToArray(), request.Path);
        <span class="hljs-keyword">return</span> (LazyCreationResponse.For(() =&gt; <span class="hljs-string">$"/walk/<span class="hljs-subst">{walk.Id}</span>"</span>, response),
            <span class="hljs-keyword">new</span> EntityFrameworkInsert&lt;WalkWithDogs&gt;(walk));
    }
}
</code></pre>
<p>Wolverine works by convention: first it loads data when it sees a <code>Load</code> or <code>LoadAsync</code> function, then it validates the request, and finally it processes the <code>Handle</code> or <code>Consume</code> function. The <code>Load</code> function is equivalent to the infrastructure code, and the <code>Handle</code> function is my logic code. The <code>Validate</code> function is a bonus to even further separate concerns.</p>
<p>Through source generation, it creates the controller for me. This means that the code is fast because there is no reflection during runtime. It also figures out what to inject into each function. It can find injectable services through the configured dependency injection framework, it can inject messages from the bus and HTTP pipeline (including query parameters and form data) and objects returned from the <code>Load</code> function. That is how the <code>Validate</code> and <code>Handle</code> methods get the request and the list of dogs.</p>
<p>What I find most impressive is that Wolverine can handle the output of the <code>Handle</code> function. In an HTTP handler, the first item in the returned tuple is the response to the client. The next items will be either messages to be sent to the configured bus or handled as a side effect. A side effect is anything related to infrastructure that is part of the transaction: saving contents to a file, updating rows in the database,... It makes this distinction by looking for the presence of the <code>ISideEffect</code> marker interface.</p>
<p>For other side effects, especially concerning external systems, I put a command on the bus and have a handler interact with the external system. This has the benefit that it runs asynchronously, it’s possible to distribute processing and it uses built-in resiliency and retry mechanisms. It also promotes reuse as multiple components can trigger the integration with a simple message.</p>
<p>These are the most important features to get started with A-Frame architecture. For more in-depth knowledge I'll refer to the <a target="_blank" href="https://wolverine.netlify.app/tutorials/">Wolverine docs</a>.</p>
<p>Next up, I’ll dive into a more complex example.</p>
]]></content:encoded></item><item><title><![CDATA[A simple A-Frame example]]></title><description><![CDATA[Now the expected structure is clear, let's take a look at the code. I will start with a minimal API implementation to demonstrate that there is no need for a framework to implement this architecture. I do know of a framework that makes A-Frame effort...]]></description><link>https://kenbonny.net/a-simple-a-frame-example</link><guid isPermaLink="true">https://kenbonny.net/a-simple-a-frame-example</guid><category><![CDATA[minimal api]]></category><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[MinimalApi]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sat, 12 Jul 2025 22:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320140439/7308aa01-94bd-49cc-84d7-391f68871841.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Now the expected structure is clear, let's take a look at the code. I will start with a minimal API implementation to demonstrate that there is no need for a framework to implement this architecture. I do know of a framework that makes A-Frame effortless to work with, I’ll go into more detail in the next blog post how this generates the controller code.</p>
<p>Let’s first get back to the simple scenario in my dog walking application. The initial endpoint will create a dog in the database. I'll start with the infrastructure code as this will be the most recognisable.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">record</span> <span class="hljs-title">CreateDog</span>(<span class="hljs-title">string</span> <span class="hljs-title">Name</span>, <span class="hljs-title">DateOnly</span> <span class="hljs-title">Birthday</span>);
app.MapPost(
    <span class="hljs-string">"/dog"</span>,
    <span class="hljs-keyword">async</span> ([FromBody] CreateDog dog, [FromServices] DogWalkingContext db) =&gt;
    {
        <span class="hljs-keyword">var</span> existingDog = <span class="hljs-keyword">await</span> db.Dogs.FirstOrDefaultAsync(d =&gt; d.Name == dog.Name &amp;&amp; d.Birthday == dog.Birthday);

        <span class="hljs-keyword">var</span> dogCreation = Dog.CreateDog(dog, existingDog);

        <span class="hljs-keyword">switch</span> (dogCreation)
        {
            <span class="hljs-keyword">case</span> DogCreated created:
                db.Dogs.Add(created.Dog);
                <span class="hljs-keyword">await</span> db.SaveChangesAsync();
                <span class="hljs-keyword">return</span> Results.Created(<span class="hljs-string">$"/dog/<span class="hljs-subst">{created.Dog.Id}</span>"</span>, created.Dog);
            <span class="hljs-keyword">case</span> DogExists exists:
                <span class="hljs-keyword">return</span> Results.Redirect(<span class="hljs-string">$"/dog/<span class="hljs-subst">{exists.Id}</span>"</span>);
        }

        <span class="hljs-keyword">return</span> Results.InternalServerError(<span class="hljs-string">"Could not determine what to do with the dog"</span>);
    })
.WithName(<span class="hljs-string">"CreateDog"</span>);
</code></pre>
<p>The endpoint retrieves a possibly existing dog and passes it, together with the create command, to the <code>CreateDog</code> handle function. The logic function decides how to handle the creation. Either I create the dog in the database and send a <em>201 Created</em> response or I send a <em>301 Redirect</em> response when the dog already exists. Should the logic returns something else, I return a <em>500 Internal Server Error</em> response.</p>
<p>Now that I have the infrastructure in place, let's look at the logic to create a dog in our system.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">abstract</span> <span class="hljs-keyword">record</span> <span class="hljs-title">DogCreation</span>;
<span class="hljs-keyword">record</span> <span class="hljs-title">DogCreated</span>(<span class="hljs-title">Dog</span> <span class="hljs-title">Dog</span>) : <span class="hljs-title">DogCreation</span>;
<span class="hljs-keyword">record</span> <span class="hljs-title">DogExists</span>(<span class="hljs-title">int</span> <span class="hljs-title">Id</span>) : <span class="hljs-title">DogCreation</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Dog</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> DateOnly Birthday { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> DogCreation <span class="hljs-title">CreateDog</span>(<span class="hljs-params">CreateDog dog, Dog? existing</span>)</span>
    {
        <span class="hljs-keyword">if</span> (existing <span class="hljs-keyword">is</span> not <span class="hljs-literal">null</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DogExists(existing.Id);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DogCreated(
            <span class="hljs-keyword">new</span> Dog
            {
                Name = dog.Name,
                Birthday = dog.Birthday
            });
    }
}
</code></pre>
<p>If a dog already exists, I return the identifier of that dog. Otherwise, I'll create a new dog. When I remove infrastructure concerns, the remaining logic is straightforward. The return structure is easy to understand and describes all possible outcomes. I like this little pattern, it reminds me of <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions">discriminated unions</a> aka <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types">union types</a>. I hope <a target="_blank" href="https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md">C# gets them soon</a> as I think functional programming paradigms are quite elegant.</p>
<p>Setting up automated tests for logic components is quite easy as there is no infrastructure that gets in the way. Infrastructure benefits more from integration tests that check all the messy side effects, while a (much faster) unit test can verify the output.</p>
<p>The readers who’ve been paying attention will have noticed that the controller and infrastructure code have interwoven in this example. Congratulations to those who spotted it. In this case, I don't mind as this is simple enough as a first example.</p>
<p>In more complex software I would put every infrastructure instruction into its own method or even class, except for Entity Framework queries. Entity Framework is an abstraction of the database, which hides the infrastructure details. I might put more complex queries in their own descriptive function, but I would not use the repository pattern.</p>
<p>Other infrastructure functionality would get their own class. This means that if I want to send an email, I’d create an <code>SendHelloEmail</code> class that would take care of sending out welcome emails. Depending on the needs of the software, I’d determine how abstract, flexible and configurable the setup of these infrastructure components should be.</p>
<h2 id="heading-an-even-simpler-scenario">An even simpler scenario</h2>
<p>The processing was easy enough to understand. I returned a reference to an endpoint that loads the details of a dog. How does that look in our A-Frame architecture? I wouldn't use A-Frame Architecture for this. Most queries are so straightforward that I don't want to bother with abstractions or indirection. I would just use a very, and I mean very, simple approach.</p>
<pre><code class="lang-csharp">app.MapGet(
        <span class="hljs-string">"/dog/{dogId}"</span>,
        <span class="hljs-keyword">async</span> (<span class="hljs-keyword">int</span> dogId, [FromServices] DogWalkingContext db) =&gt; Results.Json(<span class="hljs-keyword">await</span> db.Dogs.FindAsync(dogId)))
    .WithName(<span class="hljs-string">"GetDog"</span>);
</code></pre>
<p>Even if queries get more complicated, most don't reach the level of processing code. I'd place these in a separate file with a single function. There is the occasional exception that breaks this rule, but for those cases I'd look for a bespoke approach to the problem at hand instead of a one-size-fits-all approach. A one-size-fits-all approach either has the problem that it’s overly complex for simple scenarios or not flexible enough for the complex cases.</p>
<p>Now that the basics are clear, let's take a look at how I can make my life a lot easier with a framework that already does a lot of the heavy lifting.</p>
]]></content:encoded></item><item><title><![CDATA[What is A-Frame architecture?]]></title><description><![CDATA[A-frame architecture is a pretty simple architectural pattern: it separates interacting with infrastructure from taking decisions using logic. Between the two is a controller who orchestrates the flow of data. Everything in your code should respect t...]]></description><link>https://kenbonny.net/what-is-a-frame-architecture</link><guid isPermaLink="true">https://kenbonny.net/what-is-a-frame-architecture</guid><category><![CDATA[A-Frame]]></category><category><![CDATA[architecture]]></category><category><![CDATA[programming]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Sun, 06 Jul 2025 06:00:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320099684/4782f1d0-73b5-4c90-a270-df1b6e9aa4b8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A-frame architecture is a pretty simple <a target="_blank" href="https://en.wikipedia.org/wiki/Architectural_pattern">architectural pattern</a>: it separates interacting with infrastructure from taking decisions using logic. Between the two is a controller who orchestrates the flow of data. Everything in your code should respect that separation. As this pattern talks more about how to structure code inside a component, it’s best used in combination with an architecture that describes how to structure components. I find that <a target="_blank" href="https://www.jimmybogard.com/vertical-slice-architecture/">Vertical Slice</a>, <a target="_blank" href="https://www.youtube.com/watch?v=5OjqD-ow8GE">Modular Monolith</a> or <a target="_blank" href="https://martinfowler.com/microservices/">Microservices</a> architectures pair well with A-Frame architecture.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747318005509/ac0772c9-9e95-4a44-ad75-37c21856b781.png" alt class="image--center mx-auto" /></p>
<p>This leads to nicely separated code components, have a single responsibility and have no dependencies on other components. This makes each component easy to reason about, which in turn leads to code that is easily tested, changed and replaced. Infrastructure components tend to be more general and promote reuse, while logic is more specific to each use case.</p>
<p>For example, I can have several logic components that need to write an image to the file system. One will deal with profile pictures while another will handle uploaded photographs. Both will delegate the write operation to the same infrastructure component.</p>
<h3 id="heading-infrastructure">Infrastructure</h3>
<p>Infrastructure components (aka infrastructure) interact with external systems, read or write state and call functions with an unpredictable outcome. Examples are:</p>
<ul>
<li><p>Database calls</p>
</li>
<li><p>Sending requests over the network</p>
</li>
<li><p>Reading or writing to the file system</p>
</li>
<li><p>Retrieving environment variables</p>
</li>
<li><p>Determining date and time</p>
</li>
<li><p>Generating random numbers, ids or <a target="_blank" href="https://en.wikipedia.org/wiki/Universally_unique_identifier">uuids</a></p>
</li>
</ul>
<p>These components can be harder to test without an actual system to talk to. They’re challenging to mock or replace, both in automated tests and test environments. Infrastructure can also behave in unpredictable ways: do I have the right permissions or credentials, is there enough space on a disk, can I make the call through the firewall, etc.</p>
<p>That is why I like to wrap these systems in an abstraction that I can more easily control. Sometimes I create my own interfaces and implementations. For file system access I mostly always create an <code>IFileSystem</code> interface that have <code>Read</code> and <code>Write</code> methods, sometimes with (de)serialisation baked in. When there are good abstractions already available, I reuse those. <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/extensions/options"><code>IOptions</code></a> is invaluable for accessing settings and <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/standard/datetime/timeprovider-overview"><code>TimeProvider</code></a> is a great way to abstract time management.</p>
<p>When it comes to database access, there is the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design#the-repository-pattern">repository pattern</a>. This is a good option when I access the database directly with <a target="_blank" href="http://ADO.NET">ADO.NET</a> or <a target="_blank" href="https://www.learndapper.com">Dapper</a>. I don’t recommend using the repository pattern together with <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/entity-framework">entity framework</a> for the simple reason that a <code>DbContext</code> already is an abstraction over the database. I've seen this lead to a maze of indirection and duplication of logic. A repository with <code>Repository.Get(Expression&lt;Func&lt;Model&gt;&gt; where)</code> that has one implementation which just forwards the <code>where</code> to the <code>DbContext</code>, comes to mind.</p>
<p>When I suggest removing the unnecessary repository pattern, I receive these two arguments against it:</p>
<ol>
<li><p><em>What about reusing a query?</em> In my experience, most queries are unique. Extension methods are a great place to store reusable statements. Think filtering by a status enum or by a date range. What I'm trying to avoid is a single function with parameters for each case. <code>Search(StatusEnum? status, DateRange? bornBetween, int? idToFilterOn)</code> with <code>if</code>'s throughout the body to filter by each optional parameter. The implementation will get quite complex and confusing. Not to mention the dozens of tests to check that it works with every combination. When there is a search endpoint that needs to perform this kind of complex query, I make a specific endpoint with the complex logic inside. The code for these queries is generally not reused.</p>
</li>
<li><p><em>How do I test against the</em> <code>DbContext</code><em>?</em> Instead of using mocks/fakes/stubs, use a real database. This is what integration tests are for as there is no substitute for a real database. See the <a target="_blank" href="https://kenbonny.net/testing-a-frame-architecture-with-tunit">Testing A-Frame architecture</a> article for a detailed explanation.</p>
</li>
</ol>
<p>Keep infrastructure as simple and straightforward as possible. I prefer to have them as standalone components that do one thing and do it well. For example, a <code>FileSystem.Write(Image image)</code> should know how to serialise the image to a byte array. If there are multiple ways of serialising, then either the component can determine what serializer to use or the logic code takes that decision and passes the serializer or the serialised content to the file writer.</p>
<p>In infrastructure code, observability is my best friend. No matter how much I prepare and test, the real system will throw curveballs my way. That is why all infrastructure components should instrument <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel">OpenTelemetry</a> so I can track requests throughout systems.</p>
<h3 id="heading-logic">Logic</h3>
<p>Now that I've loaded data, it's time to make decisions based on that information. This is where logic components come into play. An alternative term is business logic, but I prefer the more generic term to keep it applicable to more scenarios.</p>
<p>A logic component is, preferably, a <a target="_blank" href="https://en.wikipedia.org/wiki/Pure_function">pure function</a>. It takes the data it needs as input and returns the decisions it has made. Most logic components are going to be quite easy to read and understand. The most important rule of logic components is that they can’t access external systems. There are a lot of similarities between logic components and the domain model from <a target="_blank" href="https://martinfowler.com/bliki/DomainDrivenDesign.html">domain driven design</a> practices.</p>
<p>Infrastructure components handle the decisions that logic components take: save data to a database, write data to a file system, notify external systems and post messages to a bus. The only exception I make is for logging as this is tightly coupled to the logic flow. I could return the log events and write them to a log stream in an infrastructure component. I find that going this far is overkill and complicates the flow. An alternative to logging is writing the in- and output of logic components to OpenTelemetry. This keeps the logic free of logging statements and still gives me all the information necessary to debug later.</p>
<p>When I have need of external libraries in my logic code, I look for ones that don’t produce side effects. For example, an image processing library should take the image as input and return it in the same format as output, I don’t want it saving the image to the file system. If the library is complex, I hide that complexity in its own class. Say I need to add a watermark to an image. I'll wrap the extensive image processing library in a class called <code>Watermark</code>. Injection or instantiation then depends on how expensive it is to create that class. I instantiate a read-only field <code>private readonly Watermark lib = new();</code>, I create it inside the function <code>var lib = new Watermark();</code> or I inject them after the data <code>Process(Model model, DateTime now, Watermark lib)</code>. I don't mind tight coupling if it makes sense. When testing this functionality, I automatically test that the logic component calls the library correctly. Because I don’t allow infrastructure, they’re still easy to set up in my tests.</p>
<p>This approach lends itself to reuse very easily: inject or instantiate the <code>Watermark</code> class and use it. It's even easy to extend to add a timestamp in another feature... Wait, hold that thought. I get why this seems like a good idea, both are adding something to an image. Unfortunately, this is a case where the functionality looks alike but is quite different in practice. Adding a watermark is something else than adding a timestamp. They’re only accidentally alike. The moment I'd start implementing this, I'd notice they’re quite different. I would take the lessons learned from the <code>Watermark</code> implementation and just create another class <code>Timestamp</code>. This is easier to maintain, evolve, replace or compose.</p>
<p>It's only when I notice that similar code appears in the codebase that I'll reflect and refactor into a shared class or component. The difference is that I'll react to what is actually there instead of prematurely optimising. This way it's more challenging to create accidental complexity.</p>
<h3 id="heading-controller">Controller</h3>
<p>This is a good point in the development process to think about the last step. I can load the necessary data and act upon it; all that I need to do is to connect the dots. This is where the controller comes into play. It will pass information from one to the other and make sure the two never meet. The controller will determine what data to load and pass it on to the logic component. Finally, it will instruct other infrastructure components based on the output of the logic component. This code is fairly straightforward and can even be automated away.</p>
<p>In practice, controllers are endpoint declarations, message handlers, WPF binding methods, cronjob entry points or equivalent. This is the place that will know where to get the data from and which logic component to pass it to.</p>
<p>Let’s put this theory into practice with a simple example.</p>
]]></content:encoded></item><item><title><![CDATA[A-Frame-mazing architecture overview]]></title><description><![CDATA[In my last projects, I’ve been using the same approach with great success. It has simplified my code, my tests and the project’s setup. In the following blog posts, I’m going to talk about A-Frame Architecture: what it is, how I use it, how to tackle...]]></description><link>https://kenbonny.net/a-frame-mazing-architecture-overview</link><guid isPermaLink="true">https://kenbonny.net/a-frame-mazing-architecture-overview</guid><category><![CDATA[architecture]]></category><category><![CDATA[A-Frame]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software development]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 30 Jun 2025 06:00:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747320125394/2d97f8b8-d3f0-4f6c-bdc1-270dff1305b6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my last projects, I’ve been using the same approach with great success. It has simplified my code, my tests and the project’s setup. In the following blog posts, I’m going to talk about A-Frame Architecture: what it is, how I use it, how to tackle more complex scenarios and which frameworks and tools help me in this process.</p>
<p>I will be using a demo project to illustrate these concepts. The app will help dog walkers record their routes, which dogs they took on each walk and which friends they met along the way.</p>
<p>Seeing as this is a demo app, I will use a basic coordinate system instead of GPS data. This will simplify the code and keep the focus on the techniques instead of going into unnecessary details of the non-existent business domain. For brevity, I'll assume you’re familiar with popular concepts such as setting up Entity Framework or how to correctly use <code>HttpClient</code>. There are numerous articles explaining those topics in more depth. I want to keep A-Frame architecture front and centre.</p>
<p>If you want to skip ahead, my <a target="_blank" href="https://github.com/KenBonny/A-Frame-Mazing-Architecture">GitHub repository with the same name</a> contains all the source code used in the following posts. I’ve also found a <a target="_blank" href="https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#a-frame-arch">blog book (too long to be called a post) from James Shore</a> that expands immensely on my idea. I’ve also drawn inspiration from that work to enhance my learning process, so I encourage you to check it out.</p>
<p>Let’s get started with explaining what A-Frame architecture is.</p>
]]></content:encoded></item><item><title><![CDATA[Why Automatic Mapping Is An Antipattern]]></title><description><![CDATA[It's been a while since I've blogged. Mainly because my life has been quite busy since my last post and I'm not sure if I'm picking up the habit as frequently as before. The reason I'm writing is that I responded to a Reddit post about using an autom...]]></description><link>https://kenbonny.net/why-automatic-mapping-is-an-antipattern</link><guid isPermaLink="true">https://kenbonny.net/why-automatic-mapping-is-an-antipattern</guid><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Mapping]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Tue, 05 Mar 2024 13:43:09 GMT</pubDate><content:encoded><![CDATA[<p>It's been a while since I've blogged. Mainly because my life has been quite busy since my last post and I'm not sure if I'm picking up the habit as frequently as before. The reason I'm writing is that I responded to a Reddit post about <a target="_blank" href="https://www.reddit.com/r/dotnet/comments/1b65cit/comment/kt9uu0g/">using an automatic mapping tool</a> and all the details I want to convey don't fit the format of a Reddit comment. The gist is that in the past few years, I've noticed that automatic mapping has quite some downsides, especially in the long run.</p>
<blockquote>
<p>Now for a little disclaimer: I don't want to single out any specific automatic mapping framework; I dislike them all equally. So if any framework contributor recognizes themselves in my description, I'm not talking about you specifically. I don't dislike you personally, just the framework that you created. 😉</p>
</blockquote>
<p>Why am I against automatic mapping? Well, there are three main reasons:</p>
<ol>
<li><p>The syntax is not intuitive</p>
</li>
<li><p>High possibility for bugs</p>
</li>
<li><p>They do not save time</p>
</li>
</ol>
<h2 id="heading-1-the-syntax-is-not-intuitive">1. The syntax is not intuitive</h2>
<p>Let's take a look at some mapping configurations. I'm just going to copy-paste from the examples that I find on the first few automatic mapping NuGets that I can find. Again, sorry for seemingly singling out specific frameworks; that is not my intention.</p>
<p>The most well-known is <a target="_blank" href="https://automapper.org">AutoMapper</a>. Let's see how their <a target="_blank" href="https://docs.automapper.org/en/stable/Projection.html">projection</a> works:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> configuration = <span class="hljs-keyword">new</span> MapperConfiguration(cfg =&gt;
  cfg.CreateMap&lt;CalendarEvent, CalendarEventForm&gt;()
    .ForMember(dest =&gt; dest.EventDate, opt =&gt; opt.MapFrom(src =&gt; src.Date.Date))
    .ForMember(dest =&gt; dest.EventHour, opt =&gt; opt.MapFrom(src =&gt; src.Date.Hour))
    .ForMember(dest =&gt; dest.EventMinute, opt =&gt; opt.MapFrom(src =&gt; src.Date.Minute)));
</code></pre>
<p>That is a lot of specifying what member goes where. I find this quite hard to follow. It's got a <code>MapperConfiguration</code> that takes a configuration function where we create a map with overrides for 3 specific members. It needs a lambda for selecting the source, then one which details what needs to be done with the output which in turn needs a lambda to select the data... I think this creates more functions than actual mapping code.</p>
<p>So let's look at another; <a target="_blank" href="https://mapperly.riok.app">Mapperly</a> is mentioned in the Reddit post. According to their <a target="_blank" href="https://mapperly.riok.app/docs/getting-started/generated-mapper-example/#the-mapper">getting started guide</a>, I need:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Enums of source and target have different numeric values -&gt; use ByName strategy to map them</span>
[<span class="hljs-meta">Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CarMapper</span>
{
    [<span class="hljs-meta">MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer))</span>] <span class="hljs-comment">// Map property with a different name in the target type</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> CarDto <span class="hljs-title">MapCarToDto</span>(<span class="hljs-params">Car car</span>)</span>;
}
</code></pre>
<p>Okay, I need a partial class (because it generates the code so I can check that in, which is actually quite nice). I specify that enums need to be mapped by name instead of value. Which in and of itself is a quite compact way of describing what I want to do. My main problem here is that I need to use attributes to say how I want to do stuff. That's not going to be a lot of hassle wiring up all the different (computed) properties. <em>/sarcasm</em> Just in case anybody was doubting if I was serious or not.</p>
<p>Maybe <a target="_blank" href="http://tinymapper.net">TinyMapper</a> can do better. Let's look at the example provided:</p>
<pre><code class="lang-csharp">TinyMapper.Bind&lt;Person, PersonDto&gt;(config =&gt;
{
    config.Ignore(x =&gt; x.Id);
    config.Ignore(x =&gt; x.Email);
    config.Bind(source =&gt; source.LastName, target =&gt; target.Surname);
    config.Bind(target =&gt; source.Emails, <span class="hljs-keyword">typeof</span>(List&lt;<span class="hljs-keyword">string</span>&gt;));
});
</code></pre>
<p>It requires less boilerplate than AutoMapper, but it's still a lot of boilerplate to specify special cases. In my experience (YMMV), most objects have at least one special case. Let's also focus on the <code>Email</code> property. It converts the emails to a list of <code>string</code>. Since it's specifying the destination type, I'm assuming the <code>Emails</code> property is not just a simple list of <code>string</code> on the source side. Why else specify the target type? I also hope somebody overwrote the <code>.ToString()</code> method so it returns the correct data of the emails. Otherwise, they'll just have a list of fully qualified namespaces instead of data.</p>
<p>So I find all of these examples not exactly readable or easy to understand. I also find that...</p>
<h2 id="heading-2-theres-a-high-probability-for-bugs">2. There's a high probability for bugs</h2>
<p>The easiest example here is that you're trying to map two incompatible properties. Now that can happen in manual mapping as well, but in the case of Mapperly, I'll have to check the generated code. Ooh, I can't convert the person's name to their date of birth. Whoops, that crashed. Now in all fairness, this is an easy mistake to spot as most of the mapping frameworks use lambdas and they do check type compatibility. So I'll forgive them this one.</p>
<p>Not the more discreet one though: renaming things. Let's say I have a person class that I want to map to a DTO.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>
{
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">PersonDto</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> FirstName, <span class="hljs-keyword">string</span> Name</span>)</span>;
</code></pre>
<p>This scenario is very easily covered by all automatic mapping frameworks, both the ones listed above and others. After some time, there is a discussion about what the <code>Name</code> property of our model should be. Is it the full name or just the last name? With our handy refactoring tools, I can rename that ambiguous property in no time to <code>LastName</code>. A quick build and all looks fine. Until I run the software and see that I suddenly have no <code>name</code> in my JSON response anymore. Everything was fine, no build errors, all references wer. I mean, we all surely write automated tests for all our little mapping scenarios, right? Right! And that's not even taking into account I might add a computed property called <code>Name</code> that returns the concatenated <code>FirstName</code> and <code>LastName</code>.</p>
<p>This is not the first, nor the last, time I will see this type of bug appear because of automatic mapping. These bugs are very easy to slip into a codebase, especially by people who are not familiar with the code and all the usages of the properties. Even if you search for all places where the original <code>Name</code> property is used, it won't show up because most mapping frameworks don't spit that information out.</p>
<p>Helpful people of Reddit pointed out that Mapperly (and probably other frameworks that use source generators) don't have this problem as they create a file with the mapping inside. Our refactoring tools will find those references, they will show up in the usage of the property and compilation will throw errors when it's not updated. So point for Mapperly (and others who do it like this).</p>
<p>Another point they made is that Mapperly can be set to strict mode where all mappings must be addressed. This prevents me from creating a configuration where an unknown property cannot be mapped. This forces me to write more <em>"easy to read"</em> mapping configuration... but only if I remember to properly configure Mapperly.</p>
<p>Wait, maybe the redeeming quality is that...</p>
<h2 id="heading-3-they-dont-save-time">3. They don't save time</h2>
<p>A lot of the frameworks claim they will save you time and spare you the tedium of writing mapping out by hand. They forget to mention the learning curve of their framework. Looking up all the configuration, how it's best used, and what the limitations are can easily add up.</p>
<p>Couple that code is more read than written with deciphering the mapping syntax and any time they save is sucked right back up into other areas. You need to learn the framework, find bugs, run into limitations, ask ChatGPT to spit out the correct syntax, wondering why that one property just doesn't want to map correctly but the others do.</p>
<p>After summing it all up, I find that automatic mapping, whichever framework you choose, is not worth the hassle.</p>
<h2 id="heading-then-what-is">Then what is?</h2>
<p>Plain, simple, <a target="_blank" href="https://boringtechnology.club/">boring</a>, easy-to-read, and reliable handwritten mapping functions. In the person example, I would create this DTO:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">record</span> <span class="hljs-title">PersonDto</span>(<span class="hljs-title">string</span> <span class="hljs-title">FirstName</span>, <span class="hljs-title">string</span> <span class="hljs-title">LastName</span>, <span class="hljs-title">string</span> <span class="hljs-title">FullName</span>)
{
    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> PersonDto <span class="hljs-title">From</span>(<span class="hljs-params">Person person</span>)</span> =&gt;
        <span class="hljs-keyword">new</span>(person.FirstName, person.LastName, <span class="hljs-string">$"<span class="hljs-subst">{person.FirstName}</span> <span class="hljs-subst">{person.LastName}</span>"</span>);
    <span class="hljs-function"><span class="hljs-keyword">internal</span> Person <span class="hljs-title">ToPerson</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-keyword">new</span>() { FirstName = FirstName, LastName = LastName };
}
</code></pre>
<p>The record ensures immutability so that I cannot change the DTO after I've created it. I have no problem adding mapping functions on the DTO so it knows how it's created or transforms back to a model. This saves me from having to create superfluous interfaces such as <code>IPersonToDtoMapper</code> or <code>IMapPersonToDto</code>, create an unnecessary implementation, forgo the wiring into the DI, and simplify where the mapping occurs because the interface is not injected and stored in a field. It's as simple as:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> people = <span class="hljs-keyword">new</span> List&lt;Person&gt;();
people.Select(PersonDto.From);
<span class="hljs-comment">// or</span>
<span class="hljs-keyword">var</span> dtos = <span class="hljs-keyword">new</span> List&lt;PersonDto&gt;();
dtos.Select(dto =&gt; dto.ToPerson());
</code></pre>
<p>This keeps the mapping logic near the edge of the application (endpoints, message queues, enterprise buses, file systems, HTTP calls,...), basically everywhere data leaves my control.</p>
<p>Quick shoutout to <a target="_blank" href="https://www.reddit.com/r/dotnet/comments/1b65cit/comment/ktawa1e/">NyanArthur</a> (and Hot-Profession4091 in a comment in this thread) for reminding me that I used the same technique they mention for mapping to and from DTOs: extension methods. I keep the extension methods near the DTOs as this is not domain knowledge, but mapping logic. This also ensures that the functions and data that change together exist together.</p>
<p>These days, I like to keep the mapping methods on the DTO, so the DTO knows how to transform itself into internal or external messages.</p>
<h2 id="heading-complex-scenarios">Complex scenarios</h2>
<p>Not every mapping is this easy or straightforward and the created model needs to be valid. Increasing complexity of validation requires increasing complexity of the chosen solution.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">So start simple and add complexity only when it arises.</div>
</div>

<p>For example, the <code>FirstName</code> should not be empty. When validation needs to happen, I like to switch from simple constructors to a static <code>Create</code> method. Then return a specific validated object from the <code>Create</code> method. It can look like a <code>Validated&lt;Person&gt;</code> so any errors can be present on the <code>Validated&lt;&gt;</code> object.</p>
<p>I prefer this explicit object over implicit exceptions. This forces the calling code to handle all scenarios and keeps exceptions for exceptional scenarios. When it's possible that there is a failure scenario, then it should be apparent.</p>
<p>When the validation logic gets even more complex, my approach would depend on whether this validation needs to be shared across multiple features or if it is specific to this feature.</p>
<h2 id="heading-avoid-exposing-domain-models-directly">Avoid exposing domain models directly</h2>
<p>In one of the comments, I read that they return the domain objects directly. I advise against doing this. A DTO is a view of the data you have, just like a page on a website or a XAML component is. The view contains all the data you want to expose, now and in the future. This allows me to change the internal model without affecting the view or contract to the outside world. In many cases, the internal model is different than the DTO.</p>
<p>When I'm experimenting with a feature, I do not want that new, unstable data to be exposed to the outside world. I want to be able to experiment with it, maybe deploy the data gathering part without making it available yet. The DTO allows me to have greater control over the contract that is available to my clients.</p>
<h2 id="heading-testing">Testing</h2>
<p>Since these are simple objects, I can quite easily create tests for them. Create a complex domain model and then map it to the different DTOs. Hint: snapshot testing with <a target="_blank" href="https://github.com/VerifyTests/Verify">Verify</a> is great for this scenario as it will fail when things don't map as planned.</p>
<p>No need to learn yet another framework, write unreadable bindings, and expose your software to subtle bugs all for the fallacy of saving time. Go forth and write boring code.</p>
]]></content:encoded></item><item><title><![CDATA[Setting Fastmail DNS records for Cloudflare]]></title><description><![CDATA[Recently I switched from Googles Workspaces to Fastmail as my mail provider. Not only am I paying less, I'm getting more in return. I love the +notation alternative and that any, not-specified, email address gets dropped into my inbox. No more settin...]]></description><link>https://kenbonny.net/setting-fastmail-dns-records-for-cloudflare</link><guid isPermaLink="true">https://kenbonny.net/setting-fastmail-dns-records-for-cloudflare</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[dns]]></category><category><![CDATA[email]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 28 Mar 2022 06:40:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647084289069/VwcaGmoyO.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I switched from Googles Workspaces to <a target="_blank" href="https://www.fastmail.com/">Fastmail</a> as my mail provider. Not only am I paying less, I'm getting more in return. I love the +notation alternative and that any, not-specified, email address gets dropped into my inbox. No more setting up info@mydomain.com, it just gets delivered to me. Setting all those DNS records is a bit time consuming, error prone and just annoying. So, scripting to the rescue.</p>
<p>Since I've been enjoying <a target="_blank" href="https://fsharp.org/">F#</a> immensely the last few months (I'll probably blog some more about that in the future), I figured I'd write an F# script to create these DNS records automatically. And good news, I kept all secret bits out of it so I can share it with you, my dearest reader!</p>
<p>The script can be found in a public <a target="_blank" href="https://gist.github.com/KenBonny/38afab3460002dfd167f8ff4a062fd98">Github Gist</a>. I won't go over every detail of the script, but you can see that I create a list of DNS records, serialise them and then post them to Cloudflare.</p>
<p>How do you use the script? Copy the script to a file ending with <code>.fsx</code>. Make sure you have at least dotnet 5 installed. Then execute the following line</p>
<pre><code>dotnet fsi [path<span class="hljs-operator">-</span>to<span class="hljs-operator">-</span>script] [domain] [cloudflare<span class="hljs-operator">-</span>auth<span class="hljs-operator">-</span>key]
</code></pre><p>Example: <code>dotnet fsi .\set-fastmail-dns-in-cloudflare.fsx kenbonny.net 00000000000-ABCdef</code></p>
<p>The <code>cloudflare-auth-key</code> can be found in your <a target="_blank" href="https://dash.cloudflare.com/profile/api-tokens">Cloudflare Dashboard</a>. They have a <a target="_blank" href="https://developers.cloudflare.com/api/tokens/create/">nice guide</a> on how to create a token.</p>
<p>Fastmail provided an easy <a target="_blank" href="https://www.fastmail.help/hc/en-us/articles/360060591153-Domains-Advanced-configuration">list of all the DNS records</a> that should be set to make everything work.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/Fastmail/status/1485706050439417869?s=20&amp;t=-phWlNhkLMkhB0tpgG9wQg">https://twitter.com/Fastmail/status/1485706050439417869?s=20&amp;t=-phWlNhkLMkhB0tpgG9wQg</a></div>
<p>Keep one thing in mind: the Cloudflare API documentation is not up to date when it comes to <a target="_blank" href="https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record">creating SRV DNS records</a>. Other DNS records require a general JSON structure:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"CNAME"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"fm1._domainkey.domain.com"</span>,
  <span class="hljs-attr">"content"</span>: <span class="hljs-string">"fm1.domain.com.dkim.fmhosted.com"</span>,
  <span class="hljs-attr">"ttl"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"priority"</span>: <span class="hljs-literal">null</span>,
  <span class="hljs-attr">"proxied"</span>: <span class="hljs-literal">false</span>
}
</code></pre>
<p>An SRV DNS record needs a <a target="_blank" href="https://community.cloudflare.com/t/error-creating-srv-dns-record/369216?u=user6631">specific JSON structure</a>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"SRV"</span>,
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"service"</span>: <span class="hljs-string">"_caldavs"</span>,
    <span class="hljs-attr">"proto"</span>: <span class="hljs-string">"_tcp"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"domain.com"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">"weight"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"port"</span>: <span class="hljs-number">443</span>,
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"caldav.fastmail.com"</span>
  }
}
</code></pre>
<p>Hopefully this saves somebody some setup headache when configuring Fastmail on Cloudflare. Including me, when I add more domains to my account. 😁</p>
]]></content:encoded></item><item><title><![CDATA[Rediscovering implicit casting]]></title><description><![CDATA[A lot of my ideas are good, but unfortunately not all of them. On my last blog post, I got feedback (via two reddit posts) that I did not think things through enough. (My wife says that happens more than I realise.) After mulling it over, I realised ...]]></description><link>https://kenbonny.net/rediscovering-implicit-casting-1</link><guid isPermaLink="true">https://kenbonny.net/rediscovering-implicit-casting-1</guid><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 05 Jul 2021 08:39:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625403867330/WdtH8yKxY.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A lot of my ideas are good, but unfortunately not all of them. On <a target="_blank" href="https://kenbonny.net/rediscovering-implicit-casting">my last blog post</a>, I got feedback (via two <a target="_blank" href="https://www.reddit.com/r/csharp/comments/nv0gqg/rediscovering_implicit_casting/">reddit</a> <a target="_blank" href="https://www.reddit.com/r/dotnet/comments/nv0gwk/rediscovering_implicit_casting/">posts</a>) that I did not think things through enough. (My wife says that happens more than I realise.) After mulling it over, I realised that the comments that I received are valid and that I need to rectify my mistake.</p>
<p>When I was thinking about ways to make Kotlins type aliases available in C#, I was too focussed on the perceived ease of representing one thing as something else. A <code>string</code> as a <code>Uri</code> for example. I did not investigate deeply enough to see that Kotlins type aliases are syntactic sugar to make concepts more explicit and not a real conversion.</p>
<p>This brings me to the first comment and I even put it right into my blog post. 🤦‍♂️</p>
<blockquote>
<p>An implicit cast should always succeed and return a result.</p>
</blockquote>
<p>This is stated originally in the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators">Microsoft guidelines</a>:</p>
<blockquote>
<p>Predefined C# implicit conversions always succeed and never throw an exception.</p>
</blockquote>
<p>Why is it important that there is no exception? Because of the implicit connection between the two types. One type should always be the other type. With an explicit cast, this can be said to be the case sometimes. I'm forcing the cast from one type to another, but it can go wrong. When I'm using an implicit cast, this should be a given.</p>
<p>For example: I can say that a <code>Uri</code> can always be represented as a <code>string</code>. I cannot say that every <code>string</code> should always be a valid <code>Uri</code>. Especially not if I say that every <code>string</code> should also be a valid <code>FileInfo</code> and a valid <code>DirectoryInfo</code> and a valid <code>Telephone</code> number. A <code>string</code> cannot be all at the same time. Yet that is what this implicit cast would indicate.</p>
<p>Therefore, my notion that all their classes should be able to transparently cast from <code>string</code> to their type is wrong.</p>
<p>A better way to do this would be to write convenience overloads for methods that take a <code>Uri</code> so it accepts a <code>string</code>.</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-title">DownloadSomething</span>
{
    <span class="hljs-function"><span class="hljs-title">Task</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-title">Data</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">string</span> location</span>)</span>
    {
        <span class="hljs-comment">// parse string to uri</span>
        Data(<span class="hljs-keyword">new</span> Uri(location));
    }
    <span class="hljs-function"><span class="hljs-title">Task</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-title">Data</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">Uri location</span>)</span>
    {
        <span class="hljs-comment">// download data</span>
    }
}
</code></pre><p>This makes it easy to pass in a <code>string</code> instead of a <code>Uri</code>, yet gives me the option to parse the <code>string</code> to make sure this is a valid <code>Uri</code>.</p>
<p>This brings me to the second point: code should not be magic. If I read the code, I should understand what it is doing, unambiguously. Suddenly going from a <code>string</code> to a <code>Uri</code>, without checking, verifying or parsing is not always intuitively. In some cases, it might be, but in a lot more, it won't be.</p>
<p>For as long as I've programmed, I've disliked clever tricks or techniques that look like magic. This should have looked like magic as well, but I was staring too blindly at the Kotlin type alias functionality. This clicked when I read this <a target="_blank" href="https://www.reddit.com/r/csharp/comments/nv0gqg/rediscovering_implicit_casting/h12hx7t?utm_source=share&amp;utm_medium=web2x&amp;context=3">really good explanation</a> about Visual Basics <code>Nothing</code> keyword.</p>
<p>To end on a positive note, I think I have found a good place for an implicit cast: inside the builder pattern. A builder should always have a valid instance of the type it has been building. So, casting a builder to the type it's creating is safe, can always be done and does not feel like magic. It works very nicely in my tests where I need to prepare several input objects. For example, an order with several items:</p>
<pre><code><span class="hljs-keyword">database</span>.<span class="hljs-keyword">Add</span>(OrderBuilder.<span class="hljs-keyword">Create</span>(orderDate, personOrdering)
                         .AddItem("Item 1", price:<span class="hljs-number">200</span>, quantity:<span class="hljs-number">10</span>)
                         .AddItem("Item 2", price:<span class="hljs-number">500</span>, quantity:<span class="hljs-number">2</span>))

<span class="hljs-keyword">class</span> OrderBuilder
{
    <span class="hljs-built_in">public</span> OrderBuilder <span class="hljs-keyword">Create</span>(DateTime orderDate, Person client)
    {
        // <span class="hljs-keyword">create</span> <span class="hljs-keyword">order</span>
        <span class="hljs-keyword">return</span> this;
    }

    <span class="hljs-built_in">public</span> OrderBuilder AddItem(string description, <span class="hljs-type">int</span> price, <span class="hljs-type">int</span> quantity)
    {
        // <span class="hljs-keyword">add</span> item <span class="hljs-keyword">to</span> <span class="hljs-keyword">order</span>
        <span class="hljs-keyword">return</span> this;
    }

    <span class="hljs-built_in">public</span> <span class="hljs-keyword">Order</span> Build()
    {
        // <span class="hljs-keyword">return</span> <span class="hljs-keyword">order</span> <span class="hljs-keyword">with</span> lines
    }

    <span class="hljs-built_in">public</span> static implicit <span class="hljs-keyword">operator</span> <span class="hljs-keyword">Order</span>(OrderBuilder builder) =&gt; builder.Build();
}
</code></pre><p>Making mistakes is never fun, but I did learn a great deal from it. I learned a lot about casting, I got a history lesson about Visual Basic and I got a lesson in humility. Now I hope others can learn from my mistake as well.</p>
]]></content:encoded></item><item><title><![CDATA[Rediscovering implicit casting]]></title><description><![CDATA[⚠ WARNING ⚠
The information in this blog post is largely wrong. Read more about it in this blog post.

With all the new goodies of C# 10 coming soon,  a co-worker noted that type aliases aren't part of this release. He's kinda jealous that Kotlin has...]]></description><link>https://kenbonny.net/rediscovering-implicit-casting</link><guid isPermaLink="true">https://kenbonny.net/rediscovering-implicit-casting</guid><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Tue, 08 Jun 2021 09:01:47 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<h3 id="warning">⚠ WARNING ⚠</h3>
<p>The information in this blog post is largely wrong. <a target="_blank" href="https://kenbonny.net/rediscovering-implicit-casting-1">Read more about it in this blog post</a>.</p>
</blockquote>
<p>With all the <a target="_blank" href="https://kenbonny.net/introducing-csharp-10">new goodies of C# 10</a> coming soon,  a co-worker noted that <a target="_blank" href="https://www.baeldung.com/kotlin/type-aliases">type aliases</a> aren't part of this release. He's kinda jealous that <a target="_blank" href="https://kotlinlang.org/">Kotlin</a> has them and us C# developers <em>have nothing that comes even remotely close</em> (loose translation from Dutch). Well, joke's on him, there are implicit conversions in the C# language!</p>
<p>Implicit conversions are pretty cool and I'm kind of bummed that not more of the dotnet framework makes use of them. There will probably be a good reason for it... or they haven't gotten around to adding it yet. But I better not get ahead of myself, so let's start with the basics.</p>
<h2 id="what-is-an-implicit-cast">What is an implicit cast?</h2>
<p>There are a number of names for implicit casting such as implicit conversion, implicit type coercion and implicit type juggling. <a target="_blank" href="https://en.wikipedia.org/wiki/Type_conversion#Implicit_type_conversion">Wikipedia</a> describes implicit casting as:</p>
<blockquote>
<p>Implicit type conversion, also known as coercion, is an automatic type conversion by the compiler. Some programming languages allow compilers to provide coercion; others require it.</p>
</blockquote>
<p>This is the <em>magic</em> behind lines such as:</p>
<pre><code><span class="hljs-type">int</span> number = <span class="hljs-number">5</span>;
<span class="hljs-type">double</span> dbl = number;
<span class="hljs-type">decimal</span> <span class="hljs-type">dec</span> = number;
</code></pre><p>This is in contrast to an explicit cast, mostly referred to as a cast or conversion or type coercion, where the type needs to be explicitly specified.</p>
<pre><code><span class="hljs-type">float</span> flt = (<span class="hljs-type">float</span>) <span class="hljs-type">dec</span>;
</code></pre><p>The <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators">guideline</a> whether a cast should be defined as implicit or explicit is that an explicit cast can fail and thus throw an exception or could lose information. So never forget to put a try-catch block around explicit conversions. An implicit cast should always succeed and return a result. I'll come back to this later.</p>
<h2 id="how-are-implicit-casts-specified">How are implicit casts specified</h2>
<p>To make it all work, the C# spec has a <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators">few keywords to make casting work</a>, whether explicit or implicit. It starts with the <code>operator</code> keyword. The base use of this keyword can be used to override or add <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/addition-operator">operations</a> to classes. An example is the + operation on <code>string</code>.</p>
<p>To override or add casting, you need to follow a specific pattern: <code>public static [implicit|explicit] operator ToType(FromType value) { /* conversion code goes here */ }</code></p>
<p>So to create an implicit cast from <code>int</code> to <code>double</code>, I would write: <code>public static implicit operator Double(int value) { /* convert int to double here */ }</code></p>
<p>All operators need to be static as they aren't used on instances of an object, but are used as a sort of extension method. They were available before extension methods, so it's not entirely true. This simile helps me understand how to use them.</p>
<p>Another benefit of these operators being static is that they cannot use or influence non-public fields or properties, which makes them all <a target="_blank" href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a>. Even if I could add side effects in any way, I do not recommend doing that as it violates my principle that there should be no magic involved in programming.</p>
<p>Programming is hard enough as it is, without involving the arcane arts. 😄</p>
<h2 id="when-is-it-useful-to-add-implicit-casts">When is it useful to add implicit casts?</h2>
<p>I've encountered a number of scenarios where implicit casts would be so convenient. On the top of my head, I'm thinking <code>string</code> to <code>Uri</code>, <code>FileInfo</code> or <code>DirectoryInfo</code>. In a lot of cases, I know which <code>string</code> refers to a url, file or directory. I would need to do <code>new Uri(uriAsString)</code> before being able to use a <code>string</code> as a <code>Uri</code>. It would allow me to create a method to fetch data from a url and pass in the <code>string</code> representation.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">private</span> T <span class="hljs-title">FetchData</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">Uri location</span>)</span>
{
  <span class="hljs-comment">// fetch data here</span>
}

<span class="hljs-comment">// call the method</span>
<span class="hljs-keyword">var</span> data = FetchData&lt;Data&gt;(<span class="hljs-string">"https://path/to/resource"</span>);
</code></pre><p>Casting a <code>string</code> to one of the mentioned types cannot fail unless I pass a <code>null</code> value, which I don't think happens a lot in these cases. In theory, I would need to make this cast explicit. I also think that there are a lot of types that do this null checking. Therefor I do not see casting a <code>null</code> value and getting an error as a valid use case to say that this would need to be an explicit cast.</p>
<p>My practical side says that the benefits of an implicit cast would outweigh the downsides for these types. This is why it would be nice if these types could add implicit conversions from <code>string</code>. P.S. don't forget to check whether a <code>Uri</code>, <code>FileInfo</code> or <code>DirectoryInfo</code> is actually a valid representation. I'm talking about handling error codes or checking <code>.Exists</code> before using it.</p>
<h2 id="how-to-add-implicit-conversion">How to add implicit conversion</h2>
<p>At the start of the article, I was talking about type aliases. I could, with a little bit more work than a Kotlin type alias, create similar code. Let's say that I have a type that is represented a lot as a piece of text, but would be nice to know what the intent behind it is. I'm going to take a phone number as an example, but it could be an email address, an address (physical, ipv4, ipv6,...) or anything in the domain at hand.</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-title">Telephone</span>
{
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Number { <span class="hljs-keyword">get</span>; }

  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Telephone</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> number</span>)</span> =&gt; Number = number;
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ToString</span>(<span class="hljs-params"></span>)</span> =&gt; Number;
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Telephone</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> number</span>)</span> =&gt; <span class="hljs-keyword">new</span>(number);
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">string</span>(<span class="hljs-params">Telephone number</span>)</span> =&gt; number.ToString();
}
</code></pre><p>Here I've got a <code>Telephone</code> class, which has a <code>Number</code> property where I'm going to store the phone number. It also has a constructor to set the number and I've overwritten the <code>ToString</code> method so I can convert back to <code>string</code> easily.</p>
<p>To implicitly convert from a <code>string</code> to a <code>Telephone</code> object, I've added the line <code>public static implicit operator Telephone(string number) =&gt; new(number);</code>. It takes a <code>string</code> and returns a <code>Telephone</code> object. To provide a cast from a <code>Telephone</code> back to a string, without calling <code>.ToString()</code>, I've added the line <code>public static implicit operator string(Telephone number) =&gt; number.ToString();</code>. Technically, I am calling the <code>.ToString()</code> method, but the compiler takes care of that for me.</p>
<p>With this in place, I can do a lot of pretty cool things.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsValidTelephoneNumber</span>(<span class="hljs-params">Telephone number</span>)</span>
{
  <span class="hljs-comment">// validation logic goes here</span>
}

<span class="hljs-comment">// use the method</span>
<span class="hljs-keyword">var</span> result = IsValidTelephoneNumber(<span class="hljs-string">"+32 123 456 789"</span>);
</code></pre><p>Let's go a little bit deeper than that. JSON deserialisation works out of the box. Say that I have a <code>Person</code> class with both a <code>Name</code> and <code>Telephone</code> property, then the expected JSON is</p>
<pre><code><span class="hljs-keyword">class</span> Person
{
  <span class="hljs-built_in">public</span> string <span class="hljs-type">Name</span> { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  <span class="hljs-built_in">public</span> Telephone Telephone { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
// serialised <span class="hljs-keyword">to</span> <span class="hljs-type">json</span>
{
  "Name": "Ken",
  "Telephone": {
    "Number": "+32 1234 567 890"
  }
}
</code></pre><p>With the implicit conversion in place, I can actually do deserialisation with JSON.NET of the following object out of the box. While I haven't tested it, my intuition says that an ASP.NET endpoint can receive the following JSON and deserialise it to a correct <code>Person</code> object.</p>
<pre><code>{
  <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"Ken"</span>,
  <span class="hljs-attr">"Telephone"</span>: <span class="hljs-string">"+32 1234 567 890"</span>
}
</code></pre><p>All of the above code can be found in <a target="_blank" href="https://gist.github.com/KenBonny/de398822839c8f01409f7f7d935b1cd3">this gist</a>. It contains a number of unit tests (written with <a target="_blank" href="https://xunit.net/">xUnit</a> and asserted with <a target="_blank" href="https://github.com/shouldly/shouldly">Shouldly</a>), so anybody can verify that this works. It even contains a custom <code>JsonConverter</code> to serialise a <code>Telephone</code> object back to the second serialisation output.</p>
<p>If <code>Url</code>, <code>FileInfo</code> or <code>DirectoryInfo</code> supported implicit casting from <code>string</code>, I could add these types to my configuration class definitions and load them directly from <em>appsettings.json</em>. No awkward conversions necessary.</p>
<h2 id="where-i-would-not-use-it">Where I would not use it</h2>
<p>Now that I've demonstrated how easy it is to add implicit (or explicit) casting to an object, there are a few situations where I would not use it.</p>
<p>I think <code>Url</code>, <code>FileInfo</code> and <code>DirectoryInfo</code> could really benefit from implicit casting because they represent a specific use case of a <code>string</code>. So what's stopping me from adding <code>DateTimeOffset</code> to the list? The number of parsing options.</p>
<p>A <code>string</code> can be a url and there are only a few ways to parse it. There are no <code>TryParse</code> methods on <code>Uri</code> (or any of the other examples). There are also only a few options when parsing a url or a path to a file.</p>
<p>In contrast, there are quite a number of ways to parse a <code>string</code> into a <code>DateTime</code> and never mind the number of added options when I use a <code>DateTimeOffset</code>. I can't just convert a <code>string</code> to a <code>DateTimeOffset</code> without specifying a number of options such as format, culture or time zone. I could take educated guesses based on the computer settings (there is a way to <code>TryParse</code> without those options), but with implicit or explicit conversion there is no way to specify those options.</p>
<p>The <code>TryParse</code> method also allows for failure, it tells me if I specified a valid date. I could use the <code>Parse</code> method and it would throw an exception if the date could not be parsed. Then I would need to add try-catch blocks, just to safely cast a <code>string</code> to a <code>DateTimeOffset</code>. I would never need to do that to a <code>Uri</code> or <code>FileInfo</code> or <code>DirectoryInfo</code>. I also hope it's general knowledge by now that <a target="_blank" href="https://www.google.com/search?q=don%27t+use+exceptions+for+flow+control">using exceptions for flow control is a bad idea</a>.</p>
<p>What I find a good middle ground is that the cast of <code>string</code> to <code>DateTimeOffset</code> would be an explicit cast as there is a good chance an exception will be thrown. I do not have this concern when trying to convert a <code>string</code> to a <code>Uri</code>, <code>FileInfo</code> or <code>DirectoryInfo</code>.</p>
<p>This also brings me to my next part: validation. I would rather build an explicit validation engine to verify that a <code>Telephone</code> is valid. I would add explicit rules such as <code>StartsWithCountryCodeOrZero</code> and <code>HasXNumberOfDigits</code>. I would not put all that logic into the constructor.</p>
<p>Adding it to the constructor would hide all the validation logic and would not provide a result with a reason why validation would fail. I would need to expose that on the <code>Telephone</code> object and it would not serve a single purpose anymore. I could throw an error, but then every cast would need to be wrapped in a try-catch block and I've touched on this problem earlier.</p>
<p>This approach would also violate the <a target="_blank" href="https://en.wikipedia.org/wiki/Single-responsibility_principle">single responsibility principle</a> as the <code>Telephone</code> class now has multiple reasons to change. It would make the code harder to understand as well. So there are a number of downsides to this approach of validation.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This might not be as succinct as Kotlins type aliases, but I think C# programmers have a lot of options creating types that represent concepts. It's not that hard to implement patterns that easily convert <code>string</code>s to more specific types such as <code>Telephone</code>, <code>Email</code> and hopefully some framework specific classes as well in the future.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing C# 10]]></title><description><![CDATA[Here is a first for my blog: Rwing offered to translate it to Chinese. So I took him up on it and you can read his translation on his blog cnblogs.com. I hope it says nice things about me. 😁

C# 10
Earlier this week, I followed a talk by Mads Torger...]]></description><link>https://kenbonny.net/introducing-csharp-10</link><guid isPermaLink="true">https://kenbonny.net/introducing-csharp-10</guid><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Fri, 30 Apr 2021 18:42:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619808066369/NL2jGN8Ih.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Here is a first for my blog: <a class="user-mention" href="https://hashnode.com/@Rwing">Rwing</a> offered to translate it to Chinese. So I took him up on it and you can read his translation on <a target="_blank" href="https://www.cnblogs.com/Rwing/p/introducing-csharp-10.html">his blog cnblogs.com</a>. I hope it says nice things about me. 😁</p>
</blockquote>
<h1 id="c-10">C# 10</h1>
<p>Earlier this week, I followed a <a target="_blank" href="https://www.youtube.com/channel/UCU0f_2rwIlvLC35GfGk_8kg">talk</a> by <a target="_blank" href="https://twitter.com/MadsTorgersen">Mads Torgersen</a> at <a target="_blank" href="https://www.meetup.com/dotnetsouthwest/">DotNet SouthWest</a>, he's the lead designer for the C# language at Microsoft. He outlined the cool new things C# 10 will contain. Let's take a quick look at some of the good things coming our way!</p>
<blockquote>
<p>Small disclaimer, most of these changes are pretty much done as he was clearly showing off. Since it's still in active development, I can't guarantee that everything will be exactly as is when C# 10 releases.</p>
</blockquote>
<p>The first thing he talked about, is how the current implementation of <code>record</code> uses a <code>class</code> (read: reference type) as the base object. There will also be a <code>record struct</code> variant available so the underlying type can be a value type. The difference is that a regular <code>record</code> will pass from function to function by reference and a <code>record struct</code> will be copied by its values. The <code>record struct</code> will include <code>with</code> support.</p>
<p>At the same time, it will be possible to add operators to <code>record</code>s. It will be available for both <code>record</code> types.</p>
<pre><code><span class="hljs-function">record <span class="hljs-title">Person</span><span class="hljs-params">(<span class="hljs-built_in">string</span> Name, <span class="hljs-built_in">string</span> Email)</span>
</span>{
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Person <span class="hljs-keyword">operator</span> +(Person first, Person second)
  {
    <span class="hljs-comment">// logic goes here</span>
  }
}
</code></pre><p>One of the goals the C#-team is focussing on, is making initialisation of objects easier. That is why it will be possible to flag properties of a <code>class</code>, <code>struct</code>, <code>record</code> or <code>record struct</code> as <code>required</code>. It makes those properties mandatory to fill in. This can be done via a constructor, or this can be done with object initialisation. The two class definitions below are equivalent. If you write it with the <code>required</code> keyword, you cannot instantiate the Person without setting the <code>Name</code> property. The compiler will throw errors and fail to compile.</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>
{
  <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  <span class="hljs-keyword">public</span> DateTime DateOfBirth { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>
{
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Person</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> name</span>)</span> =&gt; Name = name;

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  <span class="hljs-keyword">public</span> DateTime DateOfBirth { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre><p>To further improve properties, it will be possible to get rid of backing fields alltogether. The new keyword <code>field</code> will provide access to said backing field. This will be available for both setters as <code>init</code> only properties.</p>
<pre><code><span class="hljs-keyword">class</span> Person
{
  <span class="hljs-built_in">public</span> string <span class="hljs-type">Name</span> { <span class="hljs-keyword">get</span>; init =&gt; field = <span class="hljs-keyword">value</span>.Trim(); }
  <span class="hljs-built_in">public</span> DateTime DateOfBirth { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span> =&gt; field = <span class="hljs-keyword">value</span>.Date; }
}
</code></pre><p>There will be a few nifty little enhancements in the next version as well. One is that the <code>with</code> operator will support anonymous types as well.</p>
<pre><code><span class="hljs-attribute">var</span> foo = new
{
  <span class="hljs-attribute">Name</span> = <span class="hljs-string">"Foo"</span>,
  Email = <span class="hljs-string">"foo<span class="hljs-variable">@mail</span>.com"</span>
};
<span class="hljs-attribute">var</span> bar = foo with {<span class="hljs-attribute">Name</span> = <span class="hljs-string">"Bar"</span>};
</code></pre><p>It will now be possible to create a single file with namespace imports that are used everywhere. For example, if there is a popular namespace that is used in virtually every file, say <code>Microsoft.Extensions.Logging.ILogger</code>, then it will be possible to add a <code>global using Microsoft.Extensions.Logging.ILogger</code> to any <em>.cs</em> file (I suggest the <em>Program.cs</em> or a dedicated <em>Imports.cs</em>) and the logger interface will be available in the entire project. Not the solution! Nobody can predict which imports are needed anywhere, so they are grouped per project.</p>
<p>Subsequently, there will also be an optimisation to <code>namespace</code>s. Now a <code>namespace</code> requires curly braces {} to group code which entails that all code will at least be indented once. To save that tab (or four spaces, whatever, you choose) and the screen real estate, adding a <code>namespace</code> anywhere in a file, will make all code belong to that <code>namespace</code>. Research has shown that nearly all code in one file belongs to the same <code>namespace</code>. The subsequent reduction in file size because all those tabs (or spaces) are saved might not be significant for one solution (even if it contains thousands of files), but on the scale of GitHub/GitLab/BitBucket/..., I think it will save them some space. Should anybody still want to include multiple namespaces in one file, the option to use the curly braces is still available.</p>
<pre><code><span class="hljs-comment">// LegacyNamespace.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">LegacyNamespace</span>
{
  <span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>
  {
    <span class="hljs-comment">// legacy code goes here</span>
  }
}

<span class="hljs-comment">// SimplifiedNamespace.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">SimplifiedNamespace</span>;
<span class="hljs-keyword">class</span> <span class="hljs-title">Bar</span>
{
  <span class="hljs-comment">// awesome code goes here</span>
}
</code></pre><p>There are some cool updates to lambda statements coming too. The compiler will have better support for inferring lambda signatures and it will be possible to add attributes as well. It will be possible to specify explicit return types to help the compiler understand the lambda.</p>
<pre><code><span class="hljs-keyword">var</span> f = Console.WriteLine;
<span class="hljs-keyword">var</span> f = x =&gt; x; <span class="hljs-comment">// inferring the return type</span>
<span class="hljs-keyword">var</span> f = (<span class="hljs-keyword">string</span> x) =&gt; x; <span class="hljs-comment">// inferring the signature</span>
<span class="hljs-keyword">var</span> f = [NotNull] x =&gt; x; <span class="hljs-comment">// adding attributes on parameters</span>
<span class="hljs-keyword">var</span> f = [NotNull] (<span class="hljs-keyword">int</span> x) =&gt; x;
<span class="hljs-keyword">var</span> f = [<span class="hljs-keyword">return</span>: NotNull] <span class="hljs-built_in">static</span> x =&gt; x; <span class="hljs-comment">// adding attribute for a return type</span>
<span class="hljs-keyword">var</span> f = T () =&gt; <span class="hljs-keyword">default</span>; <span class="hljs-comment">// explicit return type</span>
<span class="hljs-keyword">var</span> f = ref <span class="hljs-keyword">int</span> (ref <span class="hljs-keyword">int</span> x) =&gt; ref x; <span class="hljs-comment">// using ref on structs</span>
<span class="hljs-keyword">var</span> f = <span class="hljs-keyword">int</span> (x) =&gt; x; <span class="hljs-comment">// explicitly specifying the return type of an implicit input</span>
<span class="hljs-keyword">var</span> f = <span class="hljs-built_in">static</span> <span class="hljs-keyword">void</span> (_) =&gt; Console.Write(<span class="hljs-string">"Help"</span>);
</code></pre><blockquote>
<p>Thanks at <a target="_blank" href="https://www.reddit.com/user/schooley/">Schooley</a> for suggesting a less confusing example attribute!</p>
</blockquote>
<p>Lastly, it will be possible to specify static methods and properties on interfaces. I know this will be a controversial topic, just like adding default implementations to interface. I'm not a fan of that last one. This however, could be very interesting. Imagine that you could specify the default value of an interface or specify creation methods.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IFoo</span>
</span>{
  <span class="hljs-keyword">static</span> IFoo Empty { <span class="hljs-keyword">get</span>; }
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">operator</span> +(IFoo first, IFoo second);
}
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span> : <span class="hljs-title">IFoo</span>
</span>{
  public <span class="hljs-keyword">static</span> IFoo Empty =&gt; <span class="hljs-keyword">new</span> Foo();
  public <span class="hljs-keyword">static</span> <span class="hljs-keyword">operator</span> +(IFoo first, IFoo second) =&gt; <span class="hljs-comment">/* do calculation here */</span>;
}
</code></pre><p>Personally, I like these changes. My favourite ones are the changes to <code>namespace</code> and the improvements to the interfaces. Anyway, the future is seeing sharp. Eh Eh...</p>
<p>I'll see myself out now.</p>
]]></content:encoded></item><item><title><![CDATA[Moving my blog to HashNode]]></title><description><![CDATA[As promised, the details on how to switch from WordPress to Hashnode. I wrote about the reasons why I'm switching in a previous blog post. I'm not going to repeat myself, instead I'm going to focus on all the things I had to do to switch.
Preparing t...]]></description><link>https://kenbonny.net/moving-my-blog-to-hashnode-the-tech-side</link><guid isPermaLink="true">https://kenbonny.net/moving-my-blog-to-hashnode-the-tech-side</guid><category><![CDATA[Hashnode]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[cloudflare]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 26 Apr 2021 08:51:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619352854067/PFmNlfjmOU.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As promised, the details on how to switch from <a target="_blank" href="https://wordpress.com">WordPress</a> to <a target="_blank" href="https://hashnode.com/">Hashnode</a>. I wrote about the reasons why I'm switching in a <a target="_blank" href="https://kenbonny.net/moving-my-blog-to-hashnode">previous blog post</a>. I'm not going to repeat myself, instead I'm going to focus on all the things I had to do to switch.</p>
<h2 id="preparing-the-blog-content">Preparing the blog content</h2>
<p>The first step was to get access to all my blog posts as I'm not about to manually copy ~130 posts from WP to HN. The number of things I had to fix was time consuming, if I had to do it all by hand it would have taken forever. Fortunately, there is an export functionality in WordPress to export my posts via <em>Tools &gt; Export</em>. The downside is that this generates one huge XML file and I need <a target="_blank" href="https://en.wikipedia.org/wiki/Markdown">markdown</a> files to upload to HN.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619352788707/omxKEp014.png" alt="wp export tool.png" /></p>
<p>After a bit of googling, I stumbled upon a <a target="_blank" href="https://github.com/lonekorean/wordpress-export-to-markdown">wordpress-export-to-markdown</a> tool. With a bit of experimenting, I found the right adjustments to get the output ready for HN. I just had to replace the frontmatter variable in <code>parser.js</code> to the following code.</p>
<pre><code><span class="hljs-attribute">frontmatter</span>: {
    <span class="hljs-attribute">title</span>: getPostTitle(post),
    <span class="hljs-attribute">date</span>: getPostDate(post),
    <span class="hljs-attribute">slug</span>: getPostSlug(post),
    <span class="hljs-attribute">image</span>: <span class="hljs-built_in">`https://images.kenbonny.net/${getPostSlug(post)}/cover.jpg`</span>,
    <span class="hljs-attribute">categories</span>: getCategories(post),
    <span class="hljs-attribute">tags</span>: getTags(post)
}
</code></pre><p>With the following command, I transformed the XML file into separate markdown files that represent each blog post.</p>
<pre><code><span class="hljs-string">&gt;</span> <span class="hljs-string">node</span> <span class="hljs-string">index.js</span>
<span class="hljs-string">Starting</span> <span class="hljs-string">wizard...</span>
<span class="hljs-string">?</span> <span class="hljs-string">Path</span> <span class="hljs-string">to</span> <span class="hljs-string">WordPress</span> <span class="hljs-string">export</span> <span class="hljs-string">file?</span> <span class="hljs-string">export.xml</span>
<span class="hljs-string">?</span> <span class="hljs-string">Path</span> <span class="hljs-string">to</span> <span class="hljs-string">output</span> <span class="hljs-string">folder?</span> <span class="hljs-string">output</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">year</span> <span class="hljs-string">folders?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">month</span> <span class="hljs-string">folders?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">a</span> <span class="hljs-string">folder</span> <span class="hljs-string">for</span> <span class="hljs-string">each</span> <span class="hljs-string">post?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Prefix</span> <span class="hljs-string">post</span> <span class="hljs-string">folders/files</span> <span class="hljs-string">with</span> <span class="hljs-string">date?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Save</span> <span class="hljs-string">images</span> <span class="hljs-string">attached</span> <span class="hljs-string">to</span> <span class="hljs-string">posts?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Save</span> <span class="hljs-string">images</span> <span class="hljs-string">scraped</span> <span class="hljs-string">from</span> <span class="hljs-string">post</span> <span class="hljs-string">body</span> <span class="hljs-string">content?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Include</span> <span class="hljs-string">custom</span> <span class="hljs-string">post</span> <span class="hljs-string">types</span> <span class="hljs-string">and</span> <span class="hljs-string">pages?</span> <span class="hljs-literal">No</span>
</code></pre><h2 id="preparing-the-images">Preparing the images</h2>
<p>This took care of the posts and their content, but unfortunately did nothing for the cover images. That's why I ran the transformation again, but this time I told it to download all the blog images.</p>
<pre><code><span class="hljs-string">&gt;</span> <span class="hljs-string">node</span> <span class="hljs-string">index.js</span>
<span class="hljs-string">Starting</span> <span class="hljs-string">wizard...</span>
<span class="hljs-string">?</span> <span class="hljs-string">Path</span> <span class="hljs-string">to</span> <span class="hljs-string">WordPress</span> <span class="hljs-string">export</span> <span class="hljs-string">file?</span> <span class="hljs-string">export.xml</span>
<span class="hljs-string">?</span> <span class="hljs-string">Path</span> <span class="hljs-string">to</span> <span class="hljs-string">output</span> <span class="hljs-string">folder?</span> <span class="hljs-string">output-with-images</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">year</span> <span class="hljs-string">folders?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">month</span> <span class="hljs-string">folders?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Create</span> <span class="hljs-string">a</span> <span class="hljs-string">folder</span> <span class="hljs-string">for</span> <span class="hljs-string">each</span> <span class="hljs-string">post?</span> <span class="hljs-literal">Yes</span>
<span class="hljs-string">?</span> <span class="hljs-string">Prefix</span> <span class="hljs-string">post</span> <span class="hljs-string">folders/files</span> <span class="hljs-string">with</span> <span class="hljs-string">date?</span> <span class="hljs-literal">No</span>
<span class="hljs-string">?</span> <span class="hljs-string">Save</span> <span class="hljs-string">images</span> <span class="hljs-string">attached</span> <span class="hljs-string">to</span> <span class="hljs-string">posts?</span> <span class="hljs-literal">Yes</span>
<span class="hljs-string">?</span> <span class="hljs-string">Save</span> <span class="hljs-string">images</span> <span class="hljs-string">scraped</span> <span class="hljs-string">from</span> <span class="hljs-string">post</span> <span class="hljs-string">body</span> <span class="hljs-string">content?</span> <span class="hljs-literal">Yes</span>
<span class="hljs-string">?</span> <span class="hljs-string">Include</span> <span class="hljs-string">custom</span> <span class="hljs-string">post</span> <span class="hljs-string">types</span> <span class="hljs-string">and</span> <span class="hljs-string">pages?</span> <span class="hljs-literal">No</span>
</code></pre><p>For safety, I copied the content of the <em>output-with-images</em> folder to another folder, so I could experiment with it. The first thing I did was remove all the markdown files as I'm interested in the images at this time. So I ran <code>ls *.md -Recurse -File | rm</code> in powershell to remove the unwanted files. All the images are saved to an images subfolder, I wanted them in the base folder. So I had to find a way to move the images from <strong>\\images</strong> to <strong>\</strong>. After a little poking around, I found this <a target="_blank" href="https://stackoverflow.com/questions/51346965/move-files-up-one-folder-level">stack overflow</a> answer which told me to run <code>ls . -Recurse -Include *.jpg, *.jpeg, *.png | mv -Destination { $_.Directory.Parent.FullName }</code>.</p>
<p>With all the images in the slug folder, it's time to remove the emtpy <em>images</em> folders with the powershell <code>ls images -Directory -Recurse | rm</code> command. I like clean folders. If there is something that I don't need, I like to get rid of it.</p>
<p>A few <code>git</code> commands later and my images are in a repository on which I enabled <a target="_blank" href="https://pages.github.com/">GitHub Pages</a>. Since GitHub Pages supports custom domains, I can assign it a subdomain of kenbonny.net: images.kenbonny.net. I configured this subdomain via <a target="_blank" href="https://cloudflare.com">Cloudflare</a> and called it a day.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619352808900/_ccN98PGD.png" alt="github pages for images setup.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619352818719/FEjTFvM8X.png" alt="cloudflare dns for images subdomain.png" /></p>
<p>I now have my own <a target="_blank" href="https://www.cloudflare.com/en-gb/learning/cdn/what-is-a-cdn/">CDN</a> with an upper limit of <a target="_blank" href="https://docs.github.com/en/github/managing-large-files/what-is-my-disk-quota#file-and-repository-size-limitations">5GB</a>. After 5 years of blogging, the images folder takes up 105MB of disk space. So I think I'll get a decent amount of mileage out of this.</p>
<p>The last hurdle is to make sure there is a <code>cover.jpg</code> file available. I wrote a custom dotnet console app that loops over the folders, checks if there is one image present and renames that to <em>cover.</em>. It printed the folders with multiple images that did not contain one with the name <em>cover</em>. That way I knew which folders to manually check.</p>
<h2 id="uploading-the-content">Uploading the content</h2>
<p>Now that I have my content as markdown files and the cover images are available on my own CDN, it's time to upload the content to HN. I was tempted for a while to write my own upload process using the <a target="_blank" href="https://api.hashnode.com/">graphql api</a> so I could add tags on the fly as the import process does not have support for this. There are two drawbacks to this approach:</p>
<ol>
<li>No backdate functionality: the frontmatter contains the actual publish date of the post, this backdate cannot be set through the api. This is available through the standard upload process.</li>
<li>Correlating tags is not easy: in WP I could set any tag I wanted, in HN, there is a curated list of tags (which I've grown to like). There is no 1-to-1 mapping for each tag that I've used over the years. Putting too much effort in this felt like a waste of time.</li>
</ol>
<p>So I zipped up all the markdown posts (after trying it with some example posts) and uploaded the zip file to HN import tool. After that ran for about an hour all my posts were present in HN. Now all I needed to do was go through each and every post and set the tags right and I was done... right?</p>
<p>Unfortunately, it wasn't that straightforward. Apparently I had forgotten that not every post had a cover image, so there were a number of posts with an empty cover image. There were also a few empty ones that did have a cover image, but the <em>cover</em> image was not in the jpg format that I set as default.</p>
<p>While checking the posts for missing cover images and tags, I noticed something else. During my 5 years of blogging, some WP formats have changed. How code is represented changed quite a bit over the years. In the early posts, I noticed that there were <code>[code language="csharp"][/code]</code> tags throughout my posts. In the later posts, the wp-to-markdown nicely used the ``` format. In some posts, I noticed some additional information such as html comments with WP paragraph specific info. So it was a good thing that I went through each post and fixed the broken parts myself. The automation did a lot out of the box, but keeping track of all the things WP can is an impossible task.</p>
<p>Unfortunately, only after I edited all my posts, I found out that in order to back a post up to my own GitHub repository, I need to save the post. I will not be doing that again to get them into my backup repository, I'll just wait for the tool that can back up the existing posts.</p>
<h2 id="finishing-touch">Finishing touch</h2>
<p>Now there is one last problem to worry about: legacy URLs. WP uses the format <em>////</em> while HN only supports <em>/</em>. That would mean that the link <a target="_blank" href="https://kenbonny.net/2021/02/01/moving-to-trunk-based-development">https://kenbonny.net/2021/02/01/moving-to-trunk-based-development</a> would not work after the move to HN. This would mean that I had a serious problem as I did not know how many links would break. There are people who send a link to a friend, there are browser autocomplete suggestions, linked articles would not arrive correctly, etc.</p>
<p>While I was checking all the content of the blog, the solution presented itself: <a target="_blank" href="https://developers.cloudflare.com/rules/transform">Cloudflare transform rules</a>. It's a new feature from Cloudflare that would allow rewriting of urls on the fly. After some experimenting, I found out that regex matches are not allowed for the free plan. I'd love to get a professional Cloudflare plan, but it would be just for this. That is a bit of a steep price for my simple blog.</p>
<p>Fortunately, I can solve the same thing with a <a target="_blank" href="https://workers.cloudflare.com/">Cloudflare Worker</a>. I created a Worker and added the below script. This will strip out the date format and redirct the user to the correct page. If the url does not contain the date format, it does nothing.</p>
<pre><code>addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  event.respondWith(handleRequest(event.request))
})

<span class="hljs-keyword">const</span> movedPermanently = <span class="hljs-number">301</span>;
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">const</span> dateFormat = <span class="hljs-regexp">/\/\d{4}(\/\d{1,2}){2}/i</span>
  <span class="hljs-keyword">if</span> (request.url.match(dateFormat)) {
    <span class="hljs-keyword">const</span> urlWithoutDate = request.url.replace(dateFormat, <span class="hljs-string">''</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`redirect <span class="hljs-subst">${request.url}</span> to <span class="hljs-subst">${urlWithoutDate}</span>`</span>);
    <span class="hljs-keyword">return</span> Response.redirect(urlWithoutDate, movedPermanently);
  }

  <span class="hljs-keyword">return</span> fetch(request);
}
</code></pre><p>It is now finally time to switch de domain from WP to HN. All that took was to set the domain in <a target="_blank" href="https://support.hashnode.com/docs/mapping-domain/">a HN setting</a> and pointing the CNAME DNS record from WP to HN.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Starting from now, my blog is hosted on Hashnode. If I did everything correctly, nobody should really know that this happened except for the visual changes. All links should continue to work and all content should be converted into the right format. If anybody finds any issues, let me know and I'll look into it.</p>
]]></content:encoded></item><item><title><![CDATA[Moving my blog to Hashnode]]></title><description><![CDATA[Lately I've not been very happy with WordPress as the home for my blog. It has a ton of features I don't need, it has hundreds of free premade templates of which I chose the least ugly one and it is quite complex. Each time I visited my own blog (not...]]></description><link>https://kenbonny.net/moving-my-blog-to-hashnode</link><guid isPermaLink="true">https://kenbonny.net/moving-my-blog-to-hashnode</guid><category><![CDATA[blog]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[cloudflare]]></category><category><![CDATA[WordPress]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 19 Apr 2021 08:11:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618815817590/jfIFvraz1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lately I've not been very happy with WordPress as the home for my blog. It has a ton of features I don't need, it has hundreds of free premade templates of which I chose the least ugly one and it is quite complex. Each time I visited my own blog (not something I do every week or even month), I got reminded that WordPress tracks my visitors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618815478465/7fBdqFJBU.png" alt="tracking in brave and chrome.png" /></p>
<p>So I set out to find an alternative. At first, I thought about creating my own platform. Something like Gatsby or Hugo. After looking into that for a bit, I found it too much work for a blog with my own personal thoughts. I don't want to get bogged down into maintaining a blog platform, making sure the certificates are up to date, improving speed and efficiency, getting the RSS feed right, adding support for twitter and code snippets, etc. I just want to log in, write an article with some content and publish it. I admire the guys who have the time and dedication to maintain their own platform, but I know I have too much going on in my life to find or make time for that commitment.</p>
<p>After some googling and looking around, I stumbled upon  <a target="_blank" href="https://hashnode.com">Hashnode</a> . It is simple but not limiting, I can write in markdown (which I personally like) and it has just the right kind of configurability for me. It supports RSS feeds, has a build in night mode (I don't need it personally, but I know a lot of people like it) and it's a hosted solution (meaning, I log in, write content and publish). It even offers custom domains for free!</p>
<p>While I was moving my ~130 articles from WordPress to Hashnode, which will get a more thorough technical post in the future, I noticed a number of nice little benefits. My number one: Hashnode does not track the readers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618815524630/F7GvEROkt.png" alt="no tracking in hashnode.png" /></p>
<p>A curated list of tags is another one I got used to quite quickly. This forces me to choose from the available tags, but it ensures everybody uses the same tags to make posts with relevant topics easy to find. In WordPress, I could add any tag I wanted, but it also meant the community is pretty scattered.</p>
<p>The last benefit, is that I can take backups to a GitHub repository. Right now, I would need to edit and save each post individually for it to be backed up, but there is a feature coming to back up the legacy posts. It quite literally backs the markdown up to a repository that I control. If I ever need or want to switch blogging platform, I've got my content in an easy to access format under my control. As I mentioned earlier, getting my content out of WordPress and into Hashnode was not a simple task and I like that Hashnode makes this a lot simpler. It says to me: you are free to leave if you do not like our platform. This implicitly means that they have to step up and provide a great platform. This really resonates with the way I like to work.</p>
<p>It's not all sunshine and happiness. The main thing I'm going to miss is the feature to post blog (or stories as they are called in Hashnode) at a later date. This means that I cannot finish writing a blog post and schedule it to release on Monday at 10 o'clock. I hope they add this quite fast as I'm fond of this feature.</p>
<p>The second and more problematic thing, is that Hashnode does not support the url format that WordPress uses: <code>[domain]/[year]/[month]/[day]/[slug]</code>. the <code>[year]/[month]/[day]</code> part is not added by Hashnode and I've had a bit of trouble to work around that. A big thanks to  <a target="_blank" href="https://www.cloudflare.com">Cloudflare</a> for supplying such an awesome platform that made this quite easy to work around. The details of this will come in the follow up post with more details.</p>
<p>The last thing that kept me on the fence for a little while, is the speed. Although the DOM draw happens quite fast, the Hashnode blog is heavier and takes overall longer to load. Because I like the writing experience a lot better and they don't track users, I am going through with the switch. Also, ignore my awesome editing skills. The speeds are taken by loading my lastest blog post  <a target="_blank" href="https://kenbonny.net/2021/03/22/jimmy-bogards-crossing-the-generics-divide">Jimmy Bogards crossing the generics divide</a> from both the WordPress and the Hashnode site.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618815576949/YfAIMIsHw.png" alt="speed comparison.png" /></p>
<p>Overall, I'm quite happy with how my Hashnode blog looks, shows code and handles Twitter and other references. That is why I'll be moving my blog over to the new platform next weekend. If you want to keep receiving updates whenever I post them, make sure to check that that still works after next week. I've made sure the impact is as small as possible, but I'm sure some issues are bound to pop up. Let me know when they happen and I'll fix them as soon as I can.</p>
]]></content:encoded></item><item><title><![CDATA[Jimmy Bogards crossing the generics divide]]></title><description><![CDATA[Jimmy Bogard wrote a blog quite recently about how to  inject generic components without using the generic part. As I have struggled with this in the past, I thought I’d create a little implementation to check out how his article solves the problem.
...]]></description><link>https://kenbonny.net/jimmy-bogards-crossing-the-generics-divide</link><guid isPermaLink="true">https://kenbonny.net/jimmy-bogards-crossing-the-generics-divide</guid><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[dependency injection]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 22 Mar 2021 13:45:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617382825483/GGvDIYC4s.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p> <a target="_blank" href="https://jimmybogard.com/">Jimmy Bogard</a> wrote a blog quite recently about how to  <a target="_blank" href="https://jimmybogard.com/crossing-the-generics-divide/">inject generic components without using the generic part</a>. As I have struggled with this in the past, I thought I’d create a little implementation to check out how his article solves the problem.</p>
<p>So I created a GitHub repository named  <a target="_blank" href="https://github.com/KenBonny/CrossingTheGenericsDivide">Crossing The Generics Divide</a>. I did put it in a namespace KenBonny, not (only) because I did the copy pasting from Jimmy’s blog, but to show that this is not made by Jimmy. It’s just a tribute. I did do one thing different: Jimmy uses “validators”, I replaced that by “processor” as I wanted to return a result (which is just a bit of text to differentiate between the processors).</p>
<p>The most important lesson that I learned is that the composition has my preference, but that I cannot inject the general <code>IPolicyProcessor</code> (Jimmy’s <code>IPolicyValidator</code>) directly. I need to ask for the specific <code>PolicyProcessor&lt;&gt;</code> as I cannot pass the type of <code>Policy</code> to the factory method with basic dependency injection.</p>
<p>There is an example with the <code>IPolicyProcessor</code> that comes from a factory, but this required a bit of extra work in a console application. The <code>IServiceProvider</code> does not automatically get registered if I did it manually. To get this working, I had to set up a hosting environment (as described by the  <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#call-services-from-main">Microsoft docs</a>). So if the normal execution is an asp.net project, this should work out of the box.</p>
]]></content:encoded></item><item><title><![CDATA[Moving to trunk based development]]></title><description><![CDATA[The project I'm working on is a smaller one. The current release pipeline has a number of specially named branches for each environment. This looks like it was based on GitFlow, but with some oddly specific named branches. So I started thinking about...]]></description><link>https://kenbonny.net/moving-to-trunk-based-development</link><guid isPermaLink="true">https://kenbonny.net/moving-to-trunk-based-development</guid><category><![CDATA[Azure]]></category><category><![CDATA[Devops]]></category><category><![CDATA[YAML]]></category><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 01 Feb 2021 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617382888526/QNvalyKli.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The project I'm working on is a smaller one. The current release pipeline has a number of specially named branches for each environment. This looks like it was based on GitFlow, but with some oddly specific named branches. So I started thinking about a better way to organise the releases.</p>
<p>Because the project is small, my first instinct was to consolidate on one branch. This looks a lot like <a target="_blank" href="https://trunkbaseddevelopment.com/">trunk based development</a> and that's because it is. I now only have a master branch from which I deploy to every test environment. With only me as developer and a few business people who test on it, it felt overkill to keep the development and quality assurance environment.</p>
<p>A problem arises when I talk with the business people about release management. They want to test the version in the test environment and when everything looks ok, I can deploy outside of business hours to production. At the moment, I do not have a way to enable new features in the test environment and disable them for the production environment. So I would not be able to continue development without also pushing those changes to production. The same issue arises with hotfixes, I'd need a way to push changes to production, without pushing every experimental feature on the master branch. The best approach would be to use <a target="_blank" href="https://martinfowler.com/articles/feature-toggles.html">feature toggles</a>, but this is not a change I can do overnight.</p>
<p>Queue the only other long lived branch besides the master branch: the production branch. I use feature branches taken from the master branch. When the business gives the green light that everything looks good, I can create a release branch from the master branch. When I have the release branch in place, I can wait until outside working hours before merging the release to production. Basically I create a snapshot from a well tested environment and push it to production. Should bugs in production appear, I can create a hotfix branch from the production branch and merge that back to the test environment for verification.</p>
<p>With this in structure in place, I have only 2 branches: production for the production environment and master for everything else. I can work trunk based in the testing environment and have a dedicated branch for production releases and issues. There is a temporary (and optional) release branch so I have a snapshot of a production release, but that is only for practical reasons.</p>
<p>In the next part, I describe the changes and important remarks I noticed while working towards the end result. This will start from the old implementation with different branches and releases and work towards the solution described above.</p>
<p>So now I just have to change the release pipeline. The first thing I noticed, was that there are a lot of branches that can kick off the single pipeline that is in control of the continuous integration (CI) build, the release build and the deploys to the various environments.</p>
<pre><code>trigger:
<span class="hljs-bullet">  -</span> master
<span class="hljs-bullet">  -</span> develop
<span class="hljs-bullet">  -</span> feature/<span class="hljs-emphasis">*
  - bugfix/*</span>
<span class="hljs-bullet">  -</span> hotfix/<span class="hljs-emphasis">*
  - release/*</span>
</code></pre><p>Before a pull request (PR) can be merged into the branches master or develop (both kick off a deploy to a specific environment), it needs to pass a build pipeline. I think the person who set this up, thought that there should be triggers for the CI pipeline to kick in. In Azure DevOps, I can specify a <a target="_blank" href="https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops">policy on a branch</a> that needs to run before the PR can complete. This kicks off a separate build process for the policy.</p>
<p>What this in practice does, is when I push to a feature branch (or any other described above), it kicks off a build (described in the release pipeline). Then I start a PR, which also kicks off a build (for the branch policy). I see one build too many here. This does not only cost time as it basically doubles the time for a PR build, it also costs money as the build process is a paid service (after the first 1800 free minutes). It becomes even more wasteful if I need to add changes to the PR. Remember that each push to a branch starts a build and each PR change kicks off a policy build.</p>
<p>Seeing as I do not want multiple branches for each environment (right now, the develop branch goes to the develop environment and the master branch goes to the test and production environments), I can simplify the trigger.</p>
<pre><code>trigger:
<span class="hljs-bullet">  -</span> master
<span class="hljs-bullet">  -</span> production
</code></pre><p>With the simplified triggers, I'm now left with a whole section that is only used for a CI build. As this is not needed for a deployment, I moved this to it's own pipeline. Azure DevOps supports multiple pipelines. I simply create one in the DevOps portal (or maybe through the Azure CLI, I'm not familiar with that) and point it at a yaml file.</p>
<pre><code><span class="hljs-attr">trigger:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">none</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">solution:</span> <span class="hljs-string">src/SolutionFile.sln</span>
  <span class="hljs-attr">buildPlatform:</span> <span class="hljs-string">Any</span> <span class="hljs-string">CPU</span>
  <span class="hljs-attr">buildConfiguration:</span> <span class="hljs-string">Release</span>

<span class="hljs-attr">stages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">CI_Build</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">CI</span> <span class="hljs-string">build</span> <span class="hljs-string">on</span> <span class="hljs-string">PR</span>
    <span class="hljs-attr">jobs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>
        <span class="hljs-attr">pool:</span>
          <span class="hljs-attr">vmImage:</span> <span class="hljs-string">windows-latest</span>
        <span class="hljs-attr">steps:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">NuGetToolInstaller@1</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">NuGet</span> <span class="hljs-string">Tooling</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">NuGetCommand@2</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">NuGet</span> <span class="hljs-string">Packages</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">restoreSolution:</span> <span class="hljs-string">$(solution)</span>
              <span class="hljs-attr">feedsToUse:</span> <span class="hljs-string">select</span>
              <span class="hljs-attr">vstsFeed:</span> <span class="hljs-string">feed-identifier</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">VSBuild@1</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Solution</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">solution:</span> <span class="hljs-string">$(solution)</span>
              <span class="hljs-attr">platform:</span> <span class="hljs-string">$(buildPlatform)</span>
              <span class="hljs-attr">configuration:</span> <span class="hljs-string">$(buildConfiguration)</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">VSTest@2</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Test</span> <span class="hljs-string">Suite</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">platform:</span> <span class="hljs-string">$(buildPlatform)</span>
              <span class="hljs-attr">configuration:</span> <span class="hljs-string">$(buildConfiguration)</span>
</code></pre><p>The content of the ci-pipeline.yml contains a simplified build process. I have added two variables to opt out of providing Microsoft with telemetry data about my builds. The <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/core/tools/telemetry"><code>DOTNET_CLI_TELEMETRY_OPTOUT</code></a> and <a target="_blank" href="https://github.com/dotnet/sdk/issues/9927"><code>DOTNET_SKIP_FIRST_TIME_EXPERIENCE</code></a> work together to not send information to Microsoft and speed the build up a little bit. More information can be found in the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet#environment-variables">Microsoft documentation</a>.</p>
<p>Now that I placed my CI build in a separate pipeline, my original pipeline is already looking cleaner. I'll save you all the refactoring steps, cursing and head scratching (of which there was a lot) and present the finished product. I redacted some parts as they are client specific details, it should not be difficult to figure out what goes where. Let's start with the general release-pipeline.yml file.</p>
<pre><code><span class="hljs-attr">trigger:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">production</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">solution:</span> <span class="hljs-string">src/SolutionFile.sln</span>
  <span class="hljs-attr">prodBranch:</span> <span class="hljs-string">refs/heads/production</span>

<span class="hljs-attr">stages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">pipeline/release-build.yml</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">pipeline/deploy.yml</span>
    <span class="hljs-attr">parameters:</span>
      <span class="hljs-attr">environment:</span> <span class="hljs-string">TEST</span>
      <span class="hljs-attr">condition:</span> <span class="hljs-string">and(succeeded(),</span> <span class="hljs-string">not(eq(variables.build.sourceBranch,</span> <span class="hljs-string">variables.prodBranch)))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">pipeline/deploy.yml</span>
      <span class="hljs-attr">parameters:</span>
        <span class="hljs-attr">environment:</span> <span class="hljs-string">PROD</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">and(succeeded(),</span> <span class="hljs-string">eq(variables.build.sourceBranch,</span> <span class="hljs-string">variables.prodBranch))</span>
</code></pre><p>The <code>trigger</code> and <code>variables</code> sections are pretty self explanatory, so lets look at the different stages. I won't display the release-build.yml, but safe to say it looks a lot like the CI pipeline yaml from earlier. I've added a few different steps as I want to build the website and processing services separately. This is necessary to deploy them separately later.</p>
<p>There are two deploys, one to the test environment and one for production. The test deployment runs after a successful build when it's not the production branch and the production deploy only runs after a successful build when it is the production branch. This is the only difference between the deploys.</p>
<p>The deploy.yml in the pipeline folder, contains a stage that calls two job templates: one to deploy the website and one for the processing services. The rest of this template should be pretty straightforward.</p>
<pre><code><span class="hljs-attr">parameters:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">condition:</span> <span class="hljs-string">""</span>

<span class="hljs-attr">stages:</span>
 <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}\_Deploy</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">To</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">dependsOn:</span> <span class="hljs-string">Release_Build</span>
    <span class="hljs-attr">condition:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.condition</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">pool:</span>
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">windows-latest</span>
    <span class="hljs-attr">jobs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">deploy-website.yml</span>
        <span class="hljs-attr">parameters:</span>
          <span class="hljs-attr">environment:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">deploy-processing-services.yml</span>
        <span class="hljs-attr">parameters:</span>
          <span class="hljs-attr">environment:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}</span>
</code></pre><p>The website template is just a deployment job that first cleans the target folder, then extracts the new build and finally transforms the web.config.</p>
<pre><code><span class="hljs-attr">parameters:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">serviceName:</span> <span class="hljs-string">"Client.Website"</span>
  <span class="hljs-attr">artifactPrefix:</span> <span class="hljs-string">"Client.Website.Build"</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">deployment:</span> <span class="hljs-string">Public_Website</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">public</span> <span class="hljs-string">website</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">resourceType:</span> <span class="hljs-string">VirtualMachine</span>
      <span class="hljs-attr">tags:</span> <span class="hljs-string">website</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">runOnce:</span>
        <span class="hljs-attr">deploy:</span>
          <span class="hljs-attr">steps:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DeleteFiles@1</span>
              <span class="hljs-attr">displayName:</span> <span class="hljs-string">"Delete $<span class="hljs-template-variable">{{ parameters.serviceName }}</span>"</span>
              <span class="hljs-attr">inputs:</span>
                <span class="hljs-attr">SourceFolder:</span> <span class="hljs-string">"C:\\WebSites\\$<span class="hljs-template-variable">{{ parameters.serviceName }}</span>"</span>
                <span class="hljs-attr">Contents:</span> <span class="hljs-string">"**/*"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">ExtractFiles@1</span>
              <span class="hljs-attr">displayName:</span> <span class="hljs-string">"Extract $<span class="hljs-template-variable">{{ parameters.serviceName }}</span>"</span>
              <span class="hljs-attr">inputs:</span>
                <span class="hljs-attr">archiveFilePatterns:</span> <span class="hljs-string">"$(Pipeline.Workspace)/Mooose/$<span class="hljs-template-variable">{{ parameters.artifactPrefix }}</span>.$(Build.BuildId).zip"</span>
                <span class="hljs-attr">cleanDestinationFolder:</span> <span class="hljs-literal">true</span>
                <span class="hljs-attr">destinationFolder:</span> <span class="hljs-string">"C:\\WebSites\\$<span class="hljs-template-variable">{{ parameters.serviceName }}</span>"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">FileTransform@2</span>
              <span class="hljs-attr">displayName:</span> <span class="hljs-string">"Transforming $(Environment.Name) web.config"</span>
              <span class="hljs-attr">inputs:</span>
                <span class="hljs-attr">folderPath:</span> <span class="hljs-string">'C:\\WebSites\\$<span class="hljs-template-variable">{{ parameters.serviceName }}</span>'</span>
                <span class="hljs-attr">xmlTransformationRules:</span> <span class="hljs-string">"-transform web.$(Environment.Name).config -xml web.config"</span>
</code></pre><p>The deploy-processing-services.yml is quite easy. It installs two agents that process service bus messages. One is for general processing, the other is for pdf generation. This gets its own service as this needs to go fast and can't wait for other general messages. There is an install-service.yml script that contains the powershell to stop, install and start the service. So that is easily reusable as well.</p>
<pre><code><span class="hljs-attr">parameters:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">""</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">deployment:</span> <span class="hljs-string">Processing_Services</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">processing</span> <span class="hljs-string">agents</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">${{</span> <span class="hljs-string">parameters.environment</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">resourceType:</span> <span class="hljs-string">VirtualMachine</span>
      <span class="hljs-attr">tags:</span> <span class="hljs-string">processing</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">runOnce:</span>
        <span class="hljs-attr">deploy:</span>
          <span class="hljs-attr">steps:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">install-service.yml</span>
              <span class="hljs-attr">parameters:</span>
                <span class="hljs-attr">serviceName:</span> <span class="hljs-string">Backend.Agent</span>
                <span class="hljs-attr">artifactPrefix:</span> <span class="hljs-string">client.backend.agent</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">template:</span> <span class="hljs-string">install-service.yml</span>
              <span class="hljs-attr">parameters:</span>
                <span class="hljs-attr">serviceName:</span> <span class="hljs-string">Pdf.Agent</span>
                <span class="hljs-attr">artifactPrefix:</span> <span class="hljs-string">client.pdf.agent</span>
</code></pre><p>I don't do a lot of special deployment things, but there is one area where I want to focus on: the environment.tags. I set the deployment to a specific environment name (QA or PROD, from the release-pipeline.yml) on a virtual machine (because they use machines in a local server park) and I use tags to specify which servers I deploy to.</p>
<p>The use of tags allow me to tag one server in the Azure DevOps Environments with the "website" and "processing" tags and everything gets deployed on one server. In the production environment on the other hand, those are several different servers (multiple for the website and one for the services). Yet I can use the same pipeline to deploy to different combinations. Should I ever need more processing servers, I can just add the servers to the Azure Environment, apply the correct tags and the deployment process would know what to do.</p>
<p>With a simplified pipeline, I can spend less time worrying about getting code into the right environment and focus more on getting the features right.</p>
]]></content:encoded></item><item><title><![CDATA[The end of 2020]]></title><description><![CDATA[It's been one hell of a year, one none of us will ever forget. I really hope it will be because of all the wonderful times humanity showed us it's shining side. The times friends, family and strangers showed compassion and sympathy.
At the end of the...]]></description><link>https://kenbonny.net/the-end-of-the-year-2020</link><guid isPermaLink="true">https://kenbonny.net/the-end-of-the-year-2020</guid><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Fri, 01 Jan 2021 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617383283433/BnqxVxlAg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's been one hell of a year, one none of us will ever forget. I really hope it will be because of all the wonderful times humanity showed us it's shining side. The times friends, family and strangers showed compassion and sympathy.</p>
<p>At the end of the year, I'd like to look back at what I did this past year. It's not as much as the past few years. My blogging was not that much, I wrote a few articles at the start of this year. Then I had a bit of a down period. Not just because of lil' old Corona, but also because I sold my last house and moved into another one. That took up a considerable amount of my time. I have also switched projects, which was a bit sudden (and stressful). I did come out in a better environment, if I say so myself. I have tried to keep sporting, but it's been a challenge to motivate myself. Going to a sports club really does wonders for my motivation, but they had to shut down for the time being.</p>
<p>From the bottom of my heart, I hope next year will be gentler, calmer and filled with joyous reunions of friends and loved ones. Because right now, they are in my thoughts (and Zoom calls), but unfortunately not close enough to share a laugh over a drink. Except for my loving and supporting wife, who's now constantly by my side as we both work from home.</p>
<p>I hope that everybody who reads this has their family, whomever they are, close by. So enjoy the holidays as best as you can and I'll see you next year. Fit and fresh to get vaccinated and pick life back up where we left it a year ago.</p>
<p>Happy new year, everybody!</p>
]]></content:encoded></item><item><title><![CDATA[My home office - the peripherals]]></title><description><![CDATA[After talking about my new office desk, chair and monitor arm, I wanted to highlight the other tools on my desk.

Disclaimer: I've bought all my gear myself with my own, hard earned, money. So no sponsorship here! Which is why I can give you the good...]]></description><link>https://kenbonny.net/my-home-office-the-peripherals</link><guid isPermaLink="true">https://kenbonny.net/my-home-office-the-peripherals</guid><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 09 Nov 2020 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381079636/P5RoQ6Y6G.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After talking about my new office desk, chair and monitor arm, I wanted to highlight the other tools on my desk.</p>
<blockquote>
<p>Disclaimer: I've bought all my gear myself with my own, hard earned, money. So no sponsorship here! Which is why I can give you the good and the bad.</p>
</blockquote>
<p>Because I like gaming and I love Logitech gear, I've got myself the <a target="_blank" href="https://www.logitechg.com/en-us/products/gaming-mice/g502-hero-gaming-mouse.910-005469.html">G502 Hero gaming mouse</a>. It's a great mouse and I can't decide if I like it more than my <a target="_blank" href="https://www.logitech.com/en-my/product/mx-master-2s-flow">MX Master 2S</a>. The G502 is attached to my desk and I use the MX when I take my laptop with me. The MX has a very long battery life and it can easily switch between multiple PC's, but the G502 is a lot more sensitive and it's a lot more customisable. For gaming, I love the G502, for coding and day to day use, I prefer the MX.</p>
<p>My keyboard is the <a target="_blank" href="https://www.microsoft.com/accessories/en-au/products/keyboards/comfort-curve-keyboard-3000/3tj-00019">Microsoft Comfort Curve 3000</a>, there is already an upgrade: the <a target="_blank" href="https://www.microsoft.com/accessories/en-us/products/keyboards/wireless-comfort-desktop-5050/pp4-00001">Wireless Comfort Desktop 5050</a>. I love the ergonomic design, it really is a lot more friendly on my wrists. I don't think I want to go back to a normal keyboard. The keys type very smoothly. I just wish I could disable the CAPS LOCK KEY BECAUSE... damnit it happened again. That is a complaint irrespective of the type of keyboard; normal keyboards, ergonomic ones, they all still have a CAPS LOCK KEY... damnit. I think the <a target="_blank" href="https://www.logitech.com/en-us/products/keyboards/k860-split-ergonomic.920-009166.html">Logitech Ergo K860</a> has the option to turn it off or remap it. That and it can switch between multiple PC's like the MX Master 2S mouse.</p>
<p>To improve the video recording during teleconferences, I bought myself a <a target="_blank" href="https://www.logitech.com/en-us/product/brio">Logitech BRIO</a> webcam. This is a good upgrade from my laptop camera. The image looks more crisp and it recognises me in a flash. From friends I've heard that the image over Skype is not great, but I think that might be Skype as Teams, Zoom and Discuss do produce nice video. The audio is good, but I've heard that it's a bit hollow compared to the mic on my headset.</p>
<p>My headset is a <a target="_blank" href="https://www.logitech.com/en-us/product/g933-7-1-surround-sound-gaming-headset">Logitech G933</a> surround sound one. I must say, that surround sound really is awesome. When I play video games, this really comes to life as I can clearly hear which direction the enemies are coming from. It's comfortable, even when I game for a few hours (too few and far in between). The microphone is really superb as well. It captures a nice, warm sound and filters out background noise effortlessly.</p>
<p>The reason I focused so much on connecting to multiple PC's earlier is because my laptop and PC share a desk. So I had to find a way to share my keyboard, mouse, headset and webcam between these two devices. Cue in the <a target="_blank" href="https://www.ugreen.com/products/2-in-4-out-usb-3-0-sharing-switch-box">UGreen 2 In 4 Out USB Sharing Box</a>. This little device allows me to plug in 4 USB devices: headset receiver, keyboard, mouse and webcam. With a single press of the button on top, I can switch to which device they are attached to. My PC streams to my screen on the Display Port input and my laptop connects via HDMI. Now I press the button on top of the UGreen box and I switch the input of the screen and I'm good to go. Unfortunately, I should have taken a bigger UGreen box, because if I want to share additional devices (say a USB microphone) I'll have to start choosing.</p>
<p>The only thing that is directly plugged into my PC is my Logitech G13 gaming keyboard. I received this as a birthday gift from my wife. I was looking for something like this and I kept doubting. When my birthday rolled around, she presented it to me. I've used it for every game I've played since. I hope it doesn't break as I don't think they still make these. I can't find a link to it on the Logitech site nor on local PC stores. The only reference I find is on Amazon for way too high a price. After a little fiddle with the key mappings, either to make a special profile for the game or to remap the keyboard config, I play way smoother with this device than any keyboard or gamepad. Best gift ever!</p>
<p>The last thing on my desk is the <a target="_blank" href="https://www.therooststand.com/">Roost laptop stand</a>. It's a bit pricey, but it's the best laptop stand I've ever had. I've given my wife one a few years ago and it's a very easy to set up stand. Much better control over the stands than the ones I've previously used in corporate settings. It also keeps a much better grip on my laptop.</p>
<p>Hopefully this series helps somebody make a decision while they are looking for a new keyboard or mouse, (standing) desk or ergonomic chair.</p>
]]></content:encoded></item><item><title><![CDATA[My home office - monitor arm]]></title><description><![CDATA[Third blog in a row about my office, this one is about the brand new monitor arm I got. This one took me by surprise as I did not think it would make such an impact on me.
Disclaimer: I've bought all my gear myself with my own, hard earned, money. So...]]></description><link>https://kenbonny.net/my-home-office-monitor-arm</link><guid isPermaLink="true">https://kenbonny.net/my-home-office-monitor-arm</guid><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 02 Nov 2020 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381046138/PukeahNfC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Third blog in a row about my office, this one is about the brand new monitor arm I got. This one took me by surprise as I did not think it would make such an impact on me.</p>
<p>Disclaimer: I've bought all my gear myself with my own, hard earned, money. So no sponsorship here! Which is why I can give you the good and the bad.</p>
<p>The last package to arrive is <a target="_blank" href="https://www.autonomous.ai/office-accessories/monitor-arm">the monitor arm</a>. I took a single one, as I only have one screen, my laptop screen functions as my secondary screen. I have used 2 dedicated screens in the past and this is something I want to go back to. Multiple big screens (27" and up) are a lot more practical than my small laptop screen.</p>
<p>The monitor arm can be attached as a clamp or drilled through the table. The foot of the support column can be changed so the stand can be attached how you like. The part on the underside is a metal clamp and I could easily see how that would damage my brand new table. I have placed the second foot, which has a rubber sole, between the metal clamp and the underside of the table.</p>
<p><a target="_blank" href="https://kenbonnyblog.files.wordpress.com/2020/10/img_20201018_172732.jpg"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381039190/zsqJrSdYw.jpeg" alt="Underneath the table" /></a></p>
<p>With the column installed, I can attach the arm at the right height. This part feels a little unstable as I need to clamp it on the column. This did have me worried as I installed the arm, but it has not moved since I set it up.</p>
<p><a target="_blank" href="https://kenbonnyblog.files.wordpress.com/2020/10/img_20201018_173501.jpg"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381041372/zILTCjBlq.jpeg" alt="The clamp on the column" /></a></p>
<p>Finding the right height can be a challenge though as the arm can't easily be moved up or down once it's been installed. If I loosen it up, I'll need to adjust the height with the screen attached to it. Which I can assume is tricky and has the risk of the screen falling. That's why I put it at the same height as my screen was when it was standing on it's base. That way, I knew it was at a comfortable height.</p>
<p>When I prepared the screen to be attached to the arm, I was slightly worried that the arm would not be able to carry the weight of the screen. This fear proved to be unfounded as the <a target="_blank" href="https://www.asus.com/Monitors/ROG-SWIFT-PG279Q/">Asus ROG PG279Q</a> is really light and most of the weight is in the foot. The arm can easily hold the screen up and after I tightened the bolt that controls the tilt, it hasn't budged from the angle I placed it in.</p>
<p>The Asus ROG is a great screen: it has nice colours and a good refresh rate, but I do wish I had taken the 4K version instead of the 2K version. 4K just looks a lot more smooth and is easier on the eyes. Especially when looking a whole day at code, mails and stack overflow. Maybe also a bit for gaming. But mostly for the code.</p>
<p>Attaching the screen to the arm did provide a problem. The mechanism would be super easy to use if the part that holds up the screen would stick out of the back of the screen. If you take a good look, the attachment is sunken into the case of the screen. This part slides over the end of the arm, so it's really easy to install... normally. With the cool triangular design (that you never see), the sliding mechanism is blocked by the triangular part of the back. I attached the sliding mechanism to the arm and asked my wife to hold up the screen, while I screwed it to the attachment. Luckily, the screen itself is very light, but it was a tense moment anyway.</p>
<p><a target="_blank" href="https://kenbonnyblog.files.wordpress.com/2020/10/img_20201018_174219.jpg"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381043699/aj8S19Q5v.jpeg" alt="Screen attached to the arm" /></a></p>
<p>The arm can't easily be adjusted in the height. Tilting and turning the screen side to side is very easy and I notice I use it to show my wife something if she's standing next to me so she can more easily see what's on the screen. The screen has a nice viewing angle, but staring directly at a screen instead of at an angle is always more fun.</p>
<p>Cable management is pretty easy although the plastic holders are a bit of squeeze for my HDMI cable. I made sure there is a bit of room on the end so I can turn my screen left and right without pulling on the cables. There is one cable that is not in the cable management, but that's because my display port cable is not long enough to fit into the cable management holders.</p>
<p>There is one detail that annoys me. When I put the table in the standing position, I notice the screen wobbles if I type or touch the table (put a glass down, for example). When I type more slowly, it doesn't happen, when I type harder or faster, it wobbles more noticeably. It's subtle, but I notice it when I write while standing up. I know it's the vibrations through the table and I can't do anything about it. Unfortunately, that doesn't make it any less annoying.</p>
<p>Now that my screen floats above my desk, I noticed that I have a lot more table space. The place that the foot of the screen took up is quite large and now it's available for documents, my phone and keys. Maybe a microphone if I want to upgrade my audio setup. It's a decent arm and I love the additional space on my desk, but next time I would look for an arm with a more sturdy base so it won't wobble.</p>
]]></content:encoded></item><item><title><![CDATA[My home office - the chair]]></title><description><![CDATA[Last week, I wrote about the sturdy SmartDesk 2. This week, I review the comfortable new chair I'm sitting on while typing this. Oh, spoiler alert, the chair is living up to it's expectations.

Disclaimer: I've bought all my gear myself with my own, ...]]></description><link>https://kenbonny.net/my-home-office-the-chair</link><guid isPermaLink="true">https://kenbonny.net/my-home-office-the-chair</guid><dc:creator><![CDATA[Ken Bonny]]></dc:creator><pubDate>Mon, 26 Oct 2020 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381056937/x5MnhWQ5N.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last week, I wrote about the sturdy <a target="_blank" href="https://kenbonny.net/2020/10/19/my-home-office-the-desk/">SmartDesk 2</a>. This week, I review the comfortable new chair I'm sitting on while typing this. Oh, spoiler alert, the chair is living up to it's expectations.</p>
<blockquote>
<p>Disclaimer: I've bought all my gear myself with my own, hard earned, money. So no sponsorship here! Which is why I can give you the good and the bad.</p>
</blockquote>
<p><a target="_blank" href="https://kenbonnyblog.files.wordpress.com/2020/10/img_20201018_172713.jpg"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381051642/9xLuhU3Pz.jpeg" alt="The ErgoChair 2" /></a></p>
<p>The two <a target="_blank" href="https://www.autonomous.ai/office-chairs/ergonomic-chair?option20=55">ErgoChair 2</a> chairs were next to arrive, about one and a half month after ordering them. Before, I had a very comfortable <a target="_blank" href="https://www.ikea.com/us/en/p/markus-office-chair-vissle-dark-gray-90289172/">Markus</a> chair from Ikea (not the exact model as I bought mine about 8 years ago). It was a very comfortable chair, but I have to say that the ErgoChair 2 is an upgrade all around.</p>
<p>It started when I assembled the ErgoChair. The instructions are very clear and a lot of thought has gone into the assembly process. In a little over half an hour, I was done with one chair. The star of the show is the superb tool that is supplied. It makes tightening the bolts a breeze. No need to awkwardly grip the little metal tool that is normally supplied, with this tool the bolts are tightened in a flash.</p>
<p><a target="_blank" href="https://kenbonnyblog.files.wordpress.com/2020/10/ergochair-tool.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617381054559/A11ilbS7X.png" alt="The supplied tool" /></a></p>
<p>During assembly, I did make a silly mistake: I put the arm rests on backwards on the first chair. Luckily I saw my mistake as I put the cushion (with the armrests) down. Besides my little <a target="_blank" href="https://www.dictionary.com/browse/derp">derp</a> moment, assembly went as smooth as it could have.</p>
<p>Now that I've used the chair for the past 2 months, I can say it's very pleasant to sit on. Almost everything can be adjusted. From the height of the chair, the headrest and the incline of the back, to the tilt of the cushion and back tilt tension. I'm not sure what that last one does, but it's impressive. They even have a <a target="_blank" href="https://cdn.autonomous.ai/static/upload/images/common/upload/20200514/Features_guide_-_Ergo_Chair_2_-_1600x9000ece76aa9e.mp4">very good instruction video</a> on what you can adjust and how to do it, because I didn't even mention all the settings you can tinker with.</p>
<p>There is one minor point: the armrests. Like the rest of the chair, it's very customisable. I can adjust the height and move the armrest itself horizontally in all directions. Here is my biggest annoyance so far: the armrests just slide around. Most adjustments such as moving the armrest or the headrest up and own, happen in stages. I can feel the clicks and stands as I move them. Not so when horizontally positioning the armrests. This is most distracting when I move my arm from my keyboard to my mouse or the other way. Then the armrest can change positions without intending to do it.</p>
<p>The top of the armrest is made of a soft kind of plastic which is nice to the touch, but some fabric or fake leather with a cushion would have been more comfortable. Especially when sitting in the chair for hours during a workday. I think it's strange they did not use the same material of the cushion to make the armrest more comfortable.</p>
<p>Don't get me wrong, the armrest is still comfortable and I love the chair. There's a lot of thought put into this to make it as comfortable as possible. If the good folks at Autonomous add some cushion to the armrest, they'll have the perfect chair.</p>
<p>Up next week: the monitor arm, again from the good folks over at Autonomous.</p>
]]></content:encoded></item></channel></rss>